【问题标题】:The class construction with a const member具有 const 成员的类构造
【发布时间】:2017-02-10 07:35:15
【问题描述】:

我有一个名为 Test 的类,其中有一个名为 const int 的成员 a

class Test {
public:
    const int a;
};

然后我实例化一个名为 test 的 Test 对象:

Test test;

并得到一个合理的编译错误“错误:'class Test'中未初始化的常量成员”,但是当我这样做时发生了一些奇怪的事情:

Test test = Test();

没有编译错误,发生了什么? Test() 不会像Test test; 那样调用默认构造函数,然后调用默认复制构造函数吗?我使用的编译器是“gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4”,编译器配置为c++11模式。

【问题讨论】:

  • 这些行都不应该编译:coliru.stacked-crooked.com/a/2eb481208934b212
  • 这只适用于最高 4.8.5 的 GCC (godbolt.org/g/nOUP4z vs godbolt.org/g/0ztSPK)
  • 编译器为 gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
  • @NathanOliver,你的编译器是什么
  • @kfsone:很可能这不是缺陷,而是将 GCC 中的默认语言方言更改为更现代的版本。如果不是(即如果 GCC 6 之前的默认方言是 C++98),那么这些编译器实际上需要接受默认模式下的第二次初始化。它没有任何问题。

标签: c++ constructor constants


【解决方案1】:

刚刚尝试过的 Visual Studio 对提到的两件事都给出了错误。 只需要使用如下的成员初始化列表

class Test {
public:
    const int a;
    Test():a (0)
    {
    }
};

int main()
{
    Test test;
    Test test2 = Test();
    return 0;
}

【讨论】:

  • 我在ubuntu中使用了eclipse,编译器配置为c++11模式。
【解决方案2】:
Test test = Test();

没有编译错误,发生了什么?

您的编译器有错误。代码格式不正确。 Test 的默认构造函数被隐式删除,因为它的格式不正确 - 你不能默认初始化 const int。它已在 gcc 4.9 中修复(我找不到此问题的错误报告)。

您必须为const 成员提供初始化程序,无论是通过Test{4} 之类的方式还是通过mem-initializer-list


...然后调用默认的复制构造函数?

从技术上讲,它会调用默认的移动构造函数,尽管无论如何都会忽略它。

【讨论】:

  • 值得一提的是Test test = Test{}; 可以。为什么?它似乎不属于aggregate init 的情况。
  • @vsoftco:“似乎没有下降”?为什么?在这种情况下,Test 显然是一个聚合。对于聚合{} 执行聚合初始化,它根本不依赖类构造函数。构造函数被删除这一事实不会以任何方式干扰聚合初始化。
  • @AnT 确实,但是聚合初始化发生在 rhs 上。 test = Test{}; 是复制初始化,不是吗?至少根据 cppreference,聚合初始化仅定义为 T = {param_list};T{param_list}
  • @vsoftco:是的,RHS 上的临时文件是通过聚合初始化来初始化的。然后将结果用作 LHS 复制初始化的参数。
  • @AnT 好吧,我有点困惑,以为没有复制 ctor。
【解决方案3】:

第一次初始化

Test test;

一直都是不正确的。简而言之,你不能让 const 对象处于未初始化状态。

第二次初始化

Test test = Test();

在 C++98、C++03 和 C++11 中格式正确(但请参阅下面的我的 P.S.)。然而,它在 C++14 及更高版本中格式不正确。

相关更改包含在 value-initialization 的定义中,响应 () 初始化程序执行。在 C++03(及之前)中,缺少用户定义的默认构造函数导致 value-initialization 完全忽略构造函数并作为独立于构造函数的初始化机制工作。 Test() 表达式将产生一个正确的零初始化临时对象。

然而,在 C++14 中,deleted 默认构造函数被值初始化处理,其方式与用户定义的构造函数相同:值初始化现在必须使用它,并且显然会失败,因为它已删除

C++11 8.5 (n3242)

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

——如果 T 是一个(可能 cv-qualified) 类类型(第 9 条)带有用户提供的构造函数 (12.1),然后调用 T 的默认构造函数(并且 如果 T 没有可访问的默认值,则初始化格式错误 构造函数);

——如果 T 是(可能是 cv 限定的)非联合类类型 没有用户提供的构造函数,那么对象是 零初始化,如果 T 是隐式声明的默认构造函数 是不平凡的,构造函数被调用。

...

C++14 8.5 (n3690)

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

——如果 T 是一个(可能 cv 限定)类类型(第 9 条),没有默认构造函数 (12.1) 或用户提供或删除的默认构造函数,然后 该对象是默认初始化的;

——如果 T 是一个(可能是 cv 限定的) 类类型没有用户提供或删除的默认构造函数, 然后对象被零初始化并且语义约束为 检查默认初始化,如果 T 有一个非平凡的默认值 构造函数,对象是默认初始化的;

...

(强调我的)。请注意在后一个版本中添加了“或已删除”措辞,它将具有已删除隐式默认构造函数的类从第二个项目符号点转移到第一个项目符号点。

因此,编译器在 C++14 模式下拒绝后一个初始化代码(并在 C++98/C++03 模式下接受它)。

换句话说,在您的情况下编译的第二个初始化是因为您的编译器显然配置为 C++03 模式(或之前)。

P.S. 我的 C++11 草案版本很可能(甚至很可能)已经过时。我没有 C++11 的最终版本。 C++11 的最终版本很可能包含与 C++14 相同的措辞。这也可以解释那些在 C++11 模式下拒绝它的编译器的行为。在这种情况下,分隔线在 C++03 和 C++11 之间通过。

【讨论】:

  • 我的编译器配置为c++11模式
  • @cong:嗯,显然你的编译器是那些即使在 C++11 模式下仍然接受 Test() 的编译器之一(在 C++14 模式下肯定会拒绝它)。再说一次,我没有 C++11 规范的最终版本(只有草稿),所以对我来说很难说那里使用的是 8.5 的哪个版本。
猜你喜欢
  • 2012-03-02
  • 1970-01-01
  • 2012-10-22
  • 2013-04-11
  • 1970-01-01
  • 2017-04-06
  • 2012-08-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多