c++friend-class

How to access a class's private member through one friend declared class?


I have a struct Region, and I want to access its private members in tests like googletest. Now, I have some problems:

  1. Some gtests generates dedicated test case class name, so it's hard to declare them as Region's friend.
  2. Even though we could, I'm not fond of such friend decls, since friendship doesn't inherit(just like in reality), so I have to write many friend decls for every classes or functions.

There are some ways to mitigate this, however, they are not suitable in my case:

  1. Using something like #ifdef DEBUG ... public: ... #else private:. We don't do this for some good reasons.
  2. Split Region into two parts, the new Region holds a RegionImpl. All tests that needs to access "private" members can test on RegionImpl. However, it also has some limitations too.

Now, I want to introduce the current solution of mine, and my question is totally based on this solution. The question is about how to address the drawback of my code. However, feel free to comment if you have a better idea.

The idea is that we can introduce a DebugRegion that holds a Region inside it, and it's Region's friend. The DebugRegion can be viewed as a smart pointer to Region, so it can access all Region's public members through operator->.

However, when we need to access Region's private members, we have to write a DEBUG_xxx to delegate. So we can use debug_region.DEBUG_foo() to access.

struct Region {
private:
    void foo() {}
    template <typaname T> bool bar(int z);
    static std::string foobar();
private:
    friend DebugRegion;
};

struct DebugRegion
{
    DebugRegion(Region & r)
        : region(r)
    {}
    Region * operator->() { return &region; }
    Region * operator->() const { return &region; }

    void DEBUG_foo() {
        return region.foo();
    }

    template <typaname T> bool DEBUG_bar(int z) {
        return region.bar(z);
    }

    static std::string DEBUG_foobar() {
        return Region::foobar();
    }
private:
    Region & region;
};

Now comes my question, in fact, I think create a delegate function for every private members make the solution not perfect. Can we have a "magic" oeprator-> can even access Region's private members here? I tried, but failed.

By intuition, I think maybe we should have some magic forward function to do so, however I can't come up with that function.


Solution

  • This is not ideal, but it does not require to add a DebugRegion type. Your "magic operator->" cannot be done, as far as I can tell, because friendship is not transferable.

    You can add a function template to your class-to-be-tested but not implement it:

    struct Region {
    private:
        void foo() {}
        template <typaname T> bool bar(int z);
        static std::string foobar();
    public:
        template <typename> void testRegion(); // you may want to make this const
    };
    

    Then in your gtest or whatever framework you use, you implement one testRegion for each individual test. You can even use the class name of your current test-instance, but I'll keep the example simple:

    // in RegionTest.cpp or similar
    namespace {
      struct tagRegionTestFoo {};
      struct tagRegionTestBar {};
    }
    
    template <>
    void Region::testRegion<tagRegionTestFoo>() {
      // your test impl for test "foo" with private access
    }
    
    TEST(Region, foo) {
      Region region{};
      // public manipulation of region
      region.testRegion<tagRegionTestFoo>();
    }
    
    template <>
    void Region::testRegion<tagRegionTestBar>() {
      // your test impl for test "bar" with private access
    }
    
    TEST(Region, bar) {
      Region region{};
      // public manipulation of region for test "bar"
      region.testRegion<tagRegionTestBar>();
    }
    

    You can instantiate this for as many individual tests as you like. Of course testRegion could also be a friend or static if you prefer. This can be misused, but only knowingly.