什么时候私有构造函数不是私有构造函数?
When is a private constructor not a private constructor?
假设我有一个类型,我想将其默认构造函数设为私有。我写了以下内容:
class C {
C() = default;
};
int main() {
C c; // error: C::C() is private within this context (g++)
// error: calling a private constructor of class 'C' (clang++)
// error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
auto c2 = C(); // error: as above
}
太棒了。
但是,构造函数并不像我想象的那样私有:
class C {
C() = default;
};
int main() {
C c{}; // OK on all compilers
auto c2 = C{}; // OK on all compilers
}
这让我感到非常惊讶、出乎意料且明显不受欢迎的行为。为什么这样可以?
诀窍在 C++14 8.4.2/5 [dcl.fct.def.default]:
... A function is user-provided if it is user-declared and not explicitly defaulted or
deleted on its first declaration. ...
这意味着 C
的默认构造函数实际上 不是 用户提供的,因为它在它的第一个声明中被显式默认。因此,C
没有用户提供的构造函数,因此是每个 8.5.1/1 [dcl.init.aggr]:
的聚合
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
您不是在调用默认构造函数,而是在聚合类型上使用聚合初始化。聚合类型允许有一个默认的构造函数,只要它在第一次声明的地方是默认的:
An aggregate is an array or a class (Clause [class]) with
- no user-provided constructors ([class.ctor]) (including those inherited ([namespace.udecl]) from a base class),
- no private or protected non-static data members (Clause [class.access]),
- no virtual functions ([class.virtual]), and
- no virtual, private, or protected base classes ([class.mi]).
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
因此,我们对聚合的要求是:
- 没有非public成员
- 没有虚函数
- 无虚拟或非public基地类
- 没有用户提供的构造函数被继承或以其他方式继承,这只允许构造函数是:
- 隐式声明,或
- 同时明确声明和定义为默认。
C
满足所有这些要求。
自然地,您可以通过简单地提供一个空的默认构造函数,或者在声明后将构造函数定义为默认构造函数来摆脱这种错误的默认构造行为:
class C {
C(){}
};
// --or--
class C {
C();
};
inline C::C() = default;
and answers are excellent and apply to c++11. And c++14. And c++17.
然而,在 c++20 中,情况发生了一些变化,OP 中的示例将不再编译:
class C {
C() = default;
};
C p; // always error
auto q = C(); // always error
C r{}; // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20
正如两个答案所指出的,后两个声明起作用的原因是因为 C
是一个聚合,这是聚合初始化。但是,由于 P1008 (using a motivating example not too dissimilar from the OP), the definition of aggregate changes in C++20 to, from [dcl.init.aggr]/1:
An aggregate is an array or a class ([class]) with
- no user-declared or inherited constructors ([class.ctor]),
- no private or protected direct non-static data members ([class.access]),
- no virtual functions ([class.virtual]), and
- no virtual, private, or protected base classes ([class.mi]).
强调我的。现在的要求是没有 用户声明的 构造函数,而它曾经是(正如两个用户在他们的答案中引用的并且可以在历史上查看 C++11, C++14, and C++17)没有 用户提供 构造函数。 C
的默认构造函数是用户声明的,但不是用户提供的,因此在 C++20 中不再是聚合。
这是聚合变化的另一个说明性示例:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
B
在 C++11 或 C++14 中不是聚合,因为它有一个基数 class。结果,B{}
只调用默认构造函数(用户声明但不是用户提供),它可以访问 A
受保护的默认构造函数。
在 C++17 中,作为 P0017 的结果,聚合被扩展为允许基数 classes。 B
是 C++17 中的聚合,这意味着 B{}
是聚合初始化,必须初始化所有子对象——包括 A
子对象。但是因为 A
的默认构造函数是受保护的,我们无法访问它,所以这个初始化是错误的。
在 C++20 中,由于 B
的用户声明的构造函数,它再次不再是聚合,因此 B{}
恢复为调用默认构造函数,这又很好-形成初始化。
假设我有一个类型,我想将其默认构造函数设为私有。我写了以下内容:
class C {
C() = default;
};
int main() {
C c; // error: C::C() is private within this context (g++)
// error: calling a private constructor of class 'C' (clang++)
// error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
auto c2 = C(); // error: as above
}
太棒了。
但是,构造函数并不像我想象的那样私有:
class C {
C() = default;
};
int main() {
C c{}; // OK on all compilers
auto c2 = C{}; // OK on all compilers
}
这让我感到非常惊讶、出乎意料且明显不受欢迎的行为。为什么这样可以?
诀窍在 C++14 8.4.2/5 [dcl.fct.def.default]:
... A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. ...
这意味着 C
的默认构造函数实际上 不是 用户提供的,因为它在它的第一个声明中被显式默认。因此,C
没有用户提供的构造函数,因此是每个 8.5.1/1 [dcl.init.aggr]:
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
您不是在调用默认构造函数,而是在聚合类型上使用聚合初始化。聚合类型允许有一个默认的构造函数,只要它在第一次声明的地方是默认的:
An aggregate is an array or a class (Clause [class]) with
- no user-provided constructors ([class.ctor]) (including those inherited ([namespace.udecl]) from a base class),
- no private or protected non-static data members (Clause [class.access]),
- no virtual functions ([class.virtual]), and
- no virtual, private, or protected base classes ([class.mi]).
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them ([class.ctor] [class.dtor], [class.copy]), which might mean defining them as deleted. A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. [ Note: Declaring a function as defaulted after its first declaration can provide efficient execution and concise definition while enabling a stable binary interface to an evolving code base. — end note ]
因此,我们对聚合的要求是:
- 没有非public成员
- 没有虚函数
- 无虚拟或非public基地类
- 没有用户提供的构造函数被继承或以其他方式继承,这只允许构造函数是:
- 隐式声明,或
- 同时明确声明和定义为默认。
C
满足所有这些要求。
自然地,您可以通过简单地提供一个空的默认构造函数,或者在声明后将构造函数定义为默认构造函数来摆脱这种错误的默认构造行为:
class C {
C(){}
};
// --or--
class C {
C();
};
inline C::C() = default;
然而,在 c++20 中,情况发生了一些变化,OP 中的示例将不再编译:
class C {
C() = default;
};
C p; // always error
auto q = C(); // always error
C r{}; // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20
正如两个答案所指出的,后两个声明起作用的原因是因为 C
是一个聚合,这是聚合初始化。但是,由于 P1008 (using a motivating example not too dissimilar from the OP), the definition of aggregate changes in C++20 to, from [dcl.init.aggr]/1:
An aggregate is an array or a class ([class]) with
- no user-declared or inherited constructors ([class.ctor]),
- no private or protected direct non-static data members ([class.access]),
- no virtual functions ([class.virtual]), and
- no virtual, private, or protected base classes ([class.mi]).
强调我的。现在的要求是没有 用户声明的 构造函数,而它曾经是(正如两个用户在他们的答案中引用的并且可以在历史上查看 C++11, C++14, and C++17)没有 用户提供 构造函数。 C
的默认构造函数是用户声明的,但不是用户提供的,因此在 C++20 中不再是聚合。
这是聚合变化的另一个说明性示例:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
B
在 C++11 或 C++14 中不是聚合,因为它有一个基数 class。结果,B{}
只调用默认构造函数(用户声明但不是用户提供),它可以访问 A
受保护的默认构造函数。
在 C++17 中,作为 P0017 的结果,聚合被扩展为允许基数 classes。 B
是 C++17 中的聚合,这意味着 B{}
是聚合初始化,必须初始化所有子对象——包括 A
子对象。但是因为 A
的默认构造函数是受保护的,我们无法访问它,所以这个初始化是错误的。
在 C++20 中,由于 B
的用户声明的构造函数,它再次不再是聚合,因此 B{}
恢复为调用默认构造函数,这又很好-形成初始化。