【问题标题】:Does "friending" the base class in CRTP inheritance affect the child class as well?在 CRTP 继承中“加好友”基类是否也会影响子类?
【发布时间】:2017-01-18 07:54:44
【问题描述】:

attempt to answer another question 中,我想出了一个方案来强制 CRTP 基类的子类在其构造函数中接受特定类型作为参数:使参数类型的构造函数private,将 CRTP 基类分配为friend,并将参数类型声明为基类构造函数的参数。

然而,当我试图证明这个方案通过访问冲突提供了所需的保护时,我发现即使参数类型的构造函数是私有的,子类也能够构造它:

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?
};

这个compiles without error,尽管我预计会发生访问冲突。为什么?

【问题讨论】:

    标签: c++ inheritance private friend crtp


    【解决方案1】:

    在 CRTP 继承中与基类“交友”是否也会影响子类?

    不,当然不是。友谊不是遗传的。为了说明问题,

    首先,P::P() 是一个默认的默认构造函数,它是一个trivial default constructor

    其次,P{}value initialization(C++11 起),

    (强调我的)

    2) 如果T 是具有既不是用户提供也不是删除的默认构造函数的类类型(即,它可能是具有隐式定义或默认构造函数的类),对象初始化为零,如果它有一个非平凡的默认构造函数,则默认初始化

    注意这里只有zero initialized,而不是default initializatedP 的私有默认构造函数根本不会被调用。

    如果T 是非联合类类型,则所有基类和非静态数据成员都初始化为零,所有填充都初始化为零位。构造函数(如果有)将被忽略。

    如果显式将其更改为默认初始化,则会出现访问冲突错误。

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

    简化演示

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

    LIVE

    解决方案

    您可以提供一个用户定义的默认构造函数,这是一个非平凡的构造函数,以改变值初始化的行为。

    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'
    };
    

    【讨论】:

    • 所以简而言之,听起来使用=default 为“私有构造函数”技巧提供了访问说明符漏洞。是否会从=default 切换到{}确保不能由非朋友从头开始构建?
    • @KyleStrand 是的。关键是用户定义的构造函数是不平凡的,它会改变值初始化的行为。
    • 如果您将其添加为解决方案(因为目标是防止非friend 类创建对象的实例),我会接受答案。
    • @KyleStrand 当然,已添加。
    【解决方案2】:

    你所做的与你的friend声明无关!

    如果你删除你的friend,代码编译也很好!

    这是因为空类的默认构造函数是公共的:

    来自 C++11 标准:

    如果类 X 没有用户声明的构造函数,则没有参数的构造函数被隐式声明为默认值。隐式声明的默认构造函数是其类的内联公共成员。

    如果你没有像这样的默认构造函数:

    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?
    };
    

    您将遇到“访问”违规,并且您会看到您的 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?
    

    【讨论】:

    • ...但明确声明构造函数default 声明构造函数。我认为这不算“没有用户声明的构造函数”;事实上,我认为这就是为什么他们这样说而不是说“没有用户定义构造函数”。
    猜你喜欢
    • 1970-01-01
    • 2013-10-10
    • 1970-01-01
    • 2020-10-09
    • 2012-07-25
    • 1970-01-01
    • 2012-08-30
    • 2011-12-07
    • 2019-07-10
    相关资源
    最近更新 更多