简体   繁体   中英

Does “friending” the base class in CRTP inheritance affect the child class as well?

In an attempt to answer another question , I came up with a scheme to force children of a CRTP base class to accept a particular type as a parameter in their constructors: make the parameter type's constructor private , assign the CRTP base class as a friend , and declare the parameter type as a parameter for the base class constructor as well.

However, when I tried to demonstrate that this scheme provided the desired protections via access violations, I found that even though the parameter type's constructor was private, the child class was able to construct it:

template <typename T>
class SingletonBase {
  protected: class P { friend class SingletonBase<T>; P() = default; };
  public:
     SingletonBase(P) {} 
};

class Logger: public SingletonBase<Logger> {
  using BASE = SingletonBase<Logger>;
  public:
    Logger() : BASE{P{}} {} // WHY NO ACCESS VIOLATION?
};

This compiles without error , even though I'd expect an access violation. Why?

Does “friending” the base class in CRTP inheritance affect the child class as well?

No, of course not. Friendship is not inherited. To illustrate the issue,

Firstly, P::P() is a defaulted default constructor, it's a trivial default constructor .

Secondly, P{} is value initialization (since C++11),

(emphasis mine)

2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor ;

Note it'll be only zero initialized here, not default initializated . The private default constructor of P won't be invoked at all.

If T is an non-union class type, all base classes and non-static data members are zero-initialized, and all padding is initialized to zero bits. The constructors, if any, are ignored.

If you change it to default initialization explicitly, you'll get the access violation error.

Logger() : BASE{P()} {} // error: calling a private constructor of class 'SingletonBase<Logger>::P
//               ~~

A simplified demonstration

class X { X() = default; };

int main()
{
    X x1{}; // fine
    X x2;   // error: calling a private constructor of class 'X'
}

LIVE

Solution

You can provide a user-defined default constructor, which is a non-trivial constructor, to change the behavior of value-initialization.

template <typename T>
class SingletonBase {
  protected: 
    class P { 
      friend class SingletonBase<T>; 
      P() {} // user-defined default constructor
    };
  public:
    SingletonBase(P) {} 
};

class Logger: public SingletonBase<Logger> {
  using BASE = SingletonBase<Logger>;
  public:
    Logger() : BASE{P{}} {} // error: calling a private constructor of class 'SingletonBase<Logger>::P'
};

What you have done has nothing to do with your friend statement!

If you remove your friend the code compiles also fine!

That is because a default constructor for an empty class is public:

From C++11 standard:

If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted. An implicitly-declared default constructor is an inline public member of its class.

If you have no default constructor like this:

template <typename T>
class SingletonBase
{
    protected: 
        class P
        { 
            friend class SingletonBase<T>;
            P(int){ }
        };

    public:
        SingletonBase(P) {}
};

class Logger: public SingletonBase<Logger>
{
    using BASE = SingletonBase<Logger>;

    public:
    Logger() : BASE(P{1}) {} // WHY NO ACCESS VIOLATION?
};

You will get the "access" violation and you see that your friend did not work!:

main.cpp: In constructor 'Logger::Logger()':
main.cpp:10:17: error: 'SingletonBase<T>::P::P(int) [with T = Logger]' is private
                 P(int){ }
                 ^
main.cpp:22:28: error: within this context
         Logger() : BASE(P{1}) {} // WHY NO ACCESS VIOLATION?

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