简体   繁体   English

CRTP继承中的基类的“友谊”是否也会影响子类?

[英]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. 为了尝试回答另一个问题 ,我想出了一个方案来强制CRTP基类的子代在其构造函数中接受特定类型作为参数:将参数类型的构造函数设为private ,将CRTP基类分配为friend ,并将参数类型也声明为基类构造函数的参数。

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? CRTP继承中的基类的“友谊”是否也会影响子类?

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 . 首先, P::P()是默认的默认构造函数,它是一个琐碎的默认构造函数

Secondly, P{} is value initialization (since C++11), 其次, P{}值初始化 (自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 ; 2)如果T是具有默认构造函数的类类型,该构造函数既不是用户提供的也不是用户删除的(也就是说,它可能是带有隐式定义或默认默认构造函数的类),则将该对象初始化为零,然后将其如果具有非平凡的默认构造函数,则默认初始化

Note it'll be only zero initialized here, not default initializated . 请注意,此处仅将其初始化为零 ,而不会默认初始化 The private default constructor of P won't be invoked at all. P的私有默认构造函数根本不会被调用。

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. 如果T是非联合类类型,则所有基类和非静态数据成员都将初始化为零,并且所有填充都将初始化为零位。 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! 您所做的与friend陈述无关!

If you remove your friend the code compiles also fine! 如果删除friend ,代码也可以编译!

That is because a default constructor for an empty class is public: 这是因为用于空类的默认构造函数是public的:

From C++11 standard: 从C ++ 11标准:

If there is no user-declared constructor for class X, a constructor having no parameters is implicitly declared as defaulted. 如果类X没有用户声明的构造函数,则将不带参数的构造函数隐式声明为默认值。 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!: 您将收到“访问”冲突,并且看到您的friend没有工作!:

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?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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