简体   繁体   中英

For a Nested class in C++ how do you access the private member of enclosed class?

I am a beginner in C++, I had a question regarding nested class in C++, how do you access the protected or private member of an enclosed class?

class A{
  
   class B{
       protected:
        B(){};

   };
   B var;  <===  error as the constructor B is protected  
};

One solution is making constructor of B public, but this exposes it'a scope and other function can instantiate it, which I don't want. What's a way to do deal with this ?

You can use friend class:

class A
{
    class B
    {
        friend class A;  //<-- makes A a friend of B

        protected:
            B(){};
    };

    B var; //<-- OK
};

I believe the question that was asked does not get at the real issue. It is almost an XY problem , but fortunately it has sufficient hints for the real issue to be deduced. Still, in the interest of propriety, I will address the question that was asked before addressing what I believe is the real issue.


how do you access the protected or private member of an enclosed class?

The same way you grant such access when the classes are not nested: the class with the protected or private member declares the other class (or function) as a friend . Just be aware that this is a bit of a sledgehammer approach, so it is advisable to consider alternatives first.

One solution is making constructor of B public, but this exposes it's scope and other function can instantiate it, which I don't want.

No, it doesn't expose anything outside A . You have declared B as a private type within A , which means only A can reference B . With your current setup, there is no functional difference between making A a friend of B and making all members of B public. In either case, A has full access to B and nothing outside A knows that B exists.

While I suspect that B is supposed to be public, the OP has had days to correct this detail. In addition, the OP posted a new question after this possible oversight was pointed out, so I must conclude that the OP does not regard it as an oversight.


Now we get to what I believe is the real issue: how to construct a public nested class from the outer class without allowing public construction.

What's a way to do deal with this ?

If there is a possibility that B might become a public type at some point (a reasonable thing to guard against), you should consider alternatives to friendship. One alternative is to put the constructor of B under lock-and-key . This could be extended to other private members of B , but that is perhaps not necessary.

One advantage of this approach is that access is granted to only the constructor, not to all private members. This helps preserve encapsulation. Furthermore, this approach allows construction to be handed off to helper functions, such as those from the standard library. For example, an emplace_back method for a standard container could construct a B object as long as the method was given a key. This might be more convenient than relying on friendship, depending on how B objects are to be used.

The key

The "key" is a private class of A . It is likely going to be an empty class, as it does not need functionality. All the key needs to do is exist and be inaccessible outside A .

class A{
  private:
    class KeyForB {};

    // Rest of the definition of A
};

The lock

The "lock" is a constructor that accepts the "key" as a parameter. This parameter will not be used by the constructor; all it does is signify that the caller is allowed to call the constructor.

    class B{
      public:
        /* explicit */ B(KeyForB /* unnamed */) : B() {}
      protected:
        B() {}
    };

There are a few details worth looking at. First, you might have noticed that B just used a private member of A ! How? There is a special rule that says that nested classes are considered part of the outer class, and as such they can access the private (and protected) members of the outer class – no friend declaration necessary.

Second, the lock delegates to the protected constructor. This may seem odd, but it does serve a purpose. Not only does it keep the default constructor available to B , but also it allows the compiler to optimize away the lock and key. Consider what would happen if the constructor was so large that the compiler opted to not inline it. If a function is not inlined, then parameters cannot be optimized away. In this case, that would mean that a KeyForB would need to be constructed and placed on the call stack. In contrast, the lock is so simple that it should be inlined under any level of optimization. It gets inlined to a call to B() and the unused KeyForB is eliminated from the executable.

Third, there is a comment acknowledging that some coding guidelines recommend marking as explicit most constructors that take a single parameter. This is sound advice. However, in this case, the only reason to create a KeyForB object is to construct a B object. So in this case, it might be acceptable and convenient to leave this conversion implicit.

Putting it together

The only remaining piece is to write a constructor for A , as the compiler-provided default constructor is deleted because it would be ill-formed.

class A{
  private:
    class KeyForB {};

  //public: // <-- possible future change
    class B{
      public:
        B(KeyForB) : B() {}
      protected:
        B() {}
    };

  public:
    // Default constructor
    A() : var(KeyForB{}) {}

  private:
    B var;
};

It might be worth noting that if the type of var were to change from B to std::shared_ptr<B> , this approach would not run into " make_shared for friend class throwing error ", as giving a key to make_shared would enable construction.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM