“明确”的目的是什么?对于默认构造函数?

发布于 2024-09-01 13:30:53 字数 1205 浏览 10 评论 0原文

我最近注意到 C++0x 中的一个类需要显式默认构造函数。但是,我无法想出可以隐式调用默认构造函数的方案。这似乎是一个毫无意义的说明符。我想也许它会禁止 Class c; 而支持 Class c = Class(); 但事实似乎并非如此。

C++11 FCD 中的一些相关引用,因为它对我来说更容易导航[C++03 中存在类似的文本,如果不在相同的地方]

12.3.1.3 [class.conv.ctor]

默认构造函数可以是显式构造函数;这样的构造函数将用于执行默认初始化或值初始化(8.5)。

它继续提供显式默认构造函数的示例,但它只是模仿我上面提供的示例。

8.5.6 [decl.init]

默认初始化 T 类型的对象意味着:

——如果 T 是一个(可能是 cv 限定的)类类型(第 9 条),则调用 T 的默认构造函数(如果 T 没有可访问的默认构造函数,则初始化是格式错误的);

8.5.7 [decl.init]

对 T 类型的对象进行值初始化意味着:

——如果 T 是一个(可能是 cv 限定的)类类型(第 9 条),具有用户提供的构造函数 (12.1),则调用 T 的默认构造函数(如果 T 没有可访问的默认构造函数);

在这两种情况下,标准都要求调用默认构造函数。但如果默认构造函数是非显式的,就会发生这种情况。为了完整起见:

8.5.11 [decl.init]

如果没有为对象指定初始化程序,则该对象将被默认初始化;

据我所知,这只留下了没有数据的转换。这没有道理。我能想到的最好的办法是:

void function(Class c);
int main() {
  function(); //implicitly convert from no parameter to a single parameter
}

但显然这不是 C++ 处理默认参数的方式。还有什么会使 explicit Class(); 的行为与 Class(); 不同?

生成此问题的具体示例是 std::function [20.8.14.2 func.wrap.func]。它需要多个转换构造函数,其中没有一个被标记为显式,但默认构造函数是显式的。

I recently noticed a class in C++0x that calls for an explicit default constructor. However, I'm failing to come up with a scenario in which a default constructor can be called implicitly. It seems like a rather pointless specifier. I thought maybe it would disallow Class c; in favor of Class c = Class(); but that does not appear to be the case.

Some relevant quotes from the C++11 FCD, since it is easier for me to navigate [similar text exists in C++03, if not in the same places]

12.3.1.3 [class.conv.ctor]

A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value initialization (8.5).

It goes on to provide an example of an explicit default constructor, but it simply mimics the example I provided above.

8.5.6 [decl.init]

To default-initialize an object of type T means:

— if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

8.5.7 [decl.init]

To value-initialize an object of type T means:

— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);

In both cases, the standard calls for the default constructor to be called. But that is what would happen if the default constructor were non-explicit. For completeness sake:

8.5.11 [decl.init]

If no initializer is specified for an object, the object is default-initialized;

From what I can tell, this just leaves conversion from no data. Which doesn't make sense. The best I can come up with would be the following:

void function(Class c);
int main() {
  function(); //implicitly convert from no parameter to a single parameter
}

But obviously that isn't the way C++ handles default arguments. What else is there that would make explicit Class(); behave differently from Class();?

The specific example that generated this question was std::function [20.8.14.2 func.wrap.func]. It requires several converting constructors, none of which are marked explicit, but the default constructor is.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

邮友 2024-09-08 13:30:53

这声明了一个显式默认构造函数:

struct A {
  explicit A(int a1 = 0);
};

A a = 0; /* not allowed */
A b; /* allowed */
A c(0); /* allowed */

如果没有参数,如以下示例所示,则显式默认构造函数是多余的。

struct A {
  /* explicit is redundant. */
  explicit A();
};

在一些 C++0x 草案中(我相信是 n3035),它通过以下方式产生了影响:

A a = {}; /* error! */
A b{}; /* alright */

void function(A a);
void f() { function({}); /* error! */ }

但是在 FCD 中,它们 改变了这一点(不过,我怀疑他们没有考虑到这个特殊原因),因为所有三种情况都值-初始化相应的对象。值初始化不会执行重载解析舞蹈,因此不会在显式构造函数上失败。

This declares an explicit default constructor:

struct A {
  explicit A(int a1 = 0);
};

A a = 0; /* not allowed */
A b; /* allowed */
A c(0); /* allowed */

In case there is no parameter, like in the following example, the explicit is redundant.

struct A {
  /* explicit is redundant. */
  explicit A();
};

In some C++0x draft (I believe it was n3035), it made a difference in the following way:

A a = {}; /* error! */
A b{}; /* alright */

void function(A a);
void f() { function({}); /* error! */ }

But in the FCD, they changed this (though, I suspect that they didn't have this particular reason in mind) in that all three cases value-initialize the respective object. Value-initialization doesn't do the overload-resolution dance and thus won't fail on explicit constructors.

哎呦我呸! 2024-09-08 13:30:53

除非另有明确说明,否则以下所有标准参考均指 N4659:2017 年 3 月帖子-Kona 工作草案/C++17 DIS


(此答案特别关注没有参数的显式默认构造函数)


案例 #1 [ >C++11 到 C++20]:非聚合的空 {} 复制列表初始化禁止使用显式默认构造函数

[over.match.list]/1 [强调 我的]:

当非聚合类类型 T 的对象被列表初始化时,例如
[dcl.init.list] 指定执行重载决策
根据本节中的规则,重载决策选择
构造函数分两个阶段:

  • (1.1) 最初,候选函数是类 T 的初始化列表构造函数 ([dcl.init.list]) 和参数列表
    由作为单个参数的初始值设定项列表组成。
  • (1.2) 如果没有找到可行的初始化列表构造函数,则再次执行重载决策,其中候选函数全部为
    T 的构造函数和参数列表包含
    初始化列表的元素。

如果初始值设定项列表没有元素并且 T 有默认值
构造函数,第一阶段被省略。
复制列表初始化,如果选择显式构造函数,则
初始化格式不正确。
[注意:这与其他
情况 ([over.match.ctor], [over.match.copy]),其中仅
转换构造函数被考虑用于复制初始化。这
仅当此初始化是最终初始化的一部分时,限制才适用
重载解析的结果。 - 尾注]

copy-list-initialization,带有空的braced-init-list {},用于非聚合禁止使用显式默认构造函数;例如:

struct Foo {
    virtual void notAnAggregate() const {};
    explicit Foo() {}
};

void foo(Foo) {}

int main() {
    Foo f1{};    // OK: direct-list-initialization

    // Error: converting to 'Foo' from initializer
    // list would use explicit constructor 'Foo::Foo()'
    Foo f2 = {};
    foo({});
}

尽管上面的标准引用是指 C++17,但这同样适用于 C++11、C++14 和 C++20。


情况#2 [仅限 C++17]:具有用户声明的构造函数且标记为显式 的类类型不是聚合

[dcl.init.aggr]/1 添加在 C++14 之间进行了一些更新和 C++17,主要是允许聚合从基类公开派生,但有一些限制,但也禁止聚合的显式构造函数[强调我的]:

聚合是一个数组或一个类

  • (1.1) 没有用户提供的、显式或继承的构造函数 ([class.ctor]),
  • (1.2) 没有私有或受保护的非静态数据成员(子句 [class.access]),
  • (1.3) 无虚函数,并且
  • (1.4) 没有虚拟、私有或受保护的基类 ([class.mi])。

P1008R1 起(禁止使用用户声明的构造函数进行聚合)(已为 C++20 实现),我们可能不再为聚合声明构造函数。然而,仅在 C++17 中,我们就有一个特殊的规则,即用户声明的(但不是用户提供的)构造函数是否被显式标记决定了类类型是否是聚合。例如,类类型

struct Foo {
    Foo() = default;
};

struct Bar {
    explicit Bar() = default;
};

在 C++11 到 C++20 中是聚合/非聚合,如下所示:

  • C++11: Foo & Bar 都是聚合
  • C++14:Foo & Bar 都是聚合
  • C++17:只有 Foo 是聚合(Bar 有一个 显式 构造函数)
  • C ++20:FooBar 都不是聚合(两者都有用户声明的构造函数)

Unless explicitly stated otherwise, all standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.


(This answer focus specifically on explicit default constructors which have no parameters)


Case #1 [C++11 through C++20]: Empty {} copy-list-initialization for non-aggregates prohibits use of explicit default constructors

As governed by [over.match.list]/1 [emphasis mine]:

When objects of non-aggregate class type T are list-initialized such
that [dcl.init.list] specifies that overload resolution is performed
according to the rules in this section, overload resolution selects
the constructor in two phases:

  • (1.1) Initially, the candidate functions are the initializer-list constructors ([dcl.init.list]) of the class T and the argument list
    consists of the initializer list as a single argument.
  • (1.2) If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all
    the constructors of the class T and the argument list consists of
    the elements of the initializer list.

If the initializer list has no elements and T has a default
constructor, the first phase is omitted. In
copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed.
[ Note: This differs from other
situations ([over.match.ctor], [over.match.copy]), where only
converting constructors are considered for copy-initialization. This
restriction only applies if this initialization is part of the final
result of overload resolution.  — end note ]

copy-list-initialization with an empty braced-init-list {} for non-aggregates prohibits use of explicit default constructors; e.g.:

struct Foo {
    virtual void notAnAggregate() const {};
    explicit Foo() {}
};

void foo(Foo) {}

int main() {
    Foo f1{};    // OK: direct-list-initialization

    // Error: converting to 'Foo' from initializer
    // list would use explicit constructor 'Foo::Foo()'
    Foo f2 = {};
    foo({});
}

Albeit the standard quote above refers to C++17, this likewise applies for C++11, C++14 and C++20.


Case #2 [C++17 only]: A class type with a user-declared constructor that is marked as explicit is not an aggregate

[dcl.init.aggr]/1 added was updated some between C++14 and C++17, mainly by allowing an aggregate to derive publicly from a base class, with some restrictions, but also prohibiting explicit constructors for aggregates [emphasis mine]:

An aggregate is an array or a class with

  • (1.1) no user-provided, explicit, or inherited constructors ([class.ctor]),
  • (1.2) no private or protected non-static data members (Clause [class.access]),
  • (1.3) no virtual functions, and
  • (1.4) no virtual, private, or protected base classes ([class.mi]).

As of P1008R1 (Prohibit aggregates with user-declared constructors), which has been implemented for C++20, we may no longer ever declare constructors for aggregates. In C++17 alone, however, we had the peculiar rule that whether a user-declared (but not user-provided) constructor was marked explicit decided whether the class type was an aggregate or not. E.g. the class types

struct Foo {
    Foo() = default;
};

struct Bar {
    explicit Bar() = default;
};

were aggregates/not aggregates in C++11 through C++20 as follows:

  • C++11: Foo & Bar are both aggregates
  • C++14: Foo & Bar are both aggregates
  • C++17: Only Foo is an aggregate (Bar has an explicit constructor)
  • C++20: None of Foo or Bar are aggregates (both has user-declared constructors)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文