问题
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?
回答1:
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'
};
回答2:
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?
来源:https://stackoverflow.com/questions/39423858/does-friending-the-base-class-in-crtp-inheritance-affect-the-child-class-as-we