CRTP 继承中的基类"friending"是否也会影响子类?

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

本文关键字:影响 子类 是否 friending 继承 基类 CRTP      更新时间:2023-10-16

为了回答另一个问题,我提出了一个方案,强制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?
};

编译没有错误,即使我期望访问冲突。为什么?

在CRTP继承中的基类"交朋友"是否也会影响子类?

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

首先,P::P()是一个默认的默认构造函数,它是一个简单的默认构造函数。

其次,P{}为值初始化(c++ 11起),

(强调我的)

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

注意这里只初始化为0,而不是默认初始化。P的私有默认构造函数根本不会被调用。

如果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'
}

生活<<p> 解决方案/strong>

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

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

您所做的与您的friend语句无关!

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

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

源自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?