【问题标题】:Certain case of value-initialization in C++03 doesn't call constructor?C ++ 03中的某些值初始化案例不调用构造函数?
【发布时间】:2014-02-21 17:10:57
【问题描述】:

当我讨论我的另一个问题 (Member not zeroed, a clang++ bug?) 时,我实际上想到了这个问题。那个问题是关于 C++11 值初始化的,但是当我看到有人在那里发布的 C++03 值初始化规则时,我很困惑。

C++03 的值初始化规则是:

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

  • 如果 T 是具有用户声明的构造函数 (12.1) 的类类型(第 9 条),则调用 T 的默认构造函数(并且 如果 T 没有可访问的默认值,则初始化格式错误 构造函数);
  • 如果 T 是没有用户声明的构造函数的非联合类类型,则 T 的每个非静态数据成员和基类组件都是 值初始化;
  • 如果T是数组类型,那么每个元素都是值初始化的;
  • 否则,对象被零初始化

请查看第二个项目符号,它为没有用户声明的构造函数的类型定义值初始化过程。这条规则没有提到构造函数调用。从 value-initialize 其他情况的描述或者从 default-initialize 的描述中我们可以看出,如果要调用构造函数,会在标准的正文中明确提到。我知道构造函数不会被调用的某些初始化形式(例如,聚合的 {}-initialization),但 non-union class type without a user-declared constructor 的值初始化应该是这种情况吗?这种类型的隐式声明的构造函数很容易变得不平凡。例如:

class A {
public:
    virtual void f() {}
};

根据C++03中的规则,如果在A的对象的值初始化过程中没有调用隐式声明的non-trivial构造函数,那么该对象的vptr如何获取设置? (我知道与 vptr 相关的东西都是由实现定义的,但这并没有改变我在这里试图提出的主要观点。)

(有人会争辩说,在规则中没有提及构造函数调用并不意味着构造函数不会被调用。好吧。假设构造函数将根据我可能忽略的其他规则被调用,但由于所有无论如何,成员都需要进行值初始化,这不会导致成员的构造函数被多次调用吗?)

当 C++03 到处都是 C++11 时问一个问题似乎毫无价值。是的,这是一个有效的观点。但是,如果我最终弄清楚这一点(无论我是否错以及为什么),我认为我或多或少可以学到一些东西。

编辑:也许我不应该以 vptr 为例。我的观点是,跳过对非平凡构造函数的调用不会对对象的有效性造成一些潜在的问题吗?毕竟,它被称为不平凡是有原因的。

【问题讨论】:

  • 标准引用并不排除编译器在初始化期间插入自己的magic,而设置 vtable 就是其中的一部分。
  • @Praetorian:也许我不应该以 vptr 为例。我的观点是,跳过对非平凡构造函数的调用不会对对象的有效性造成一些潜在的问题吗?毕竟,它被称为不平凡是有原因的。

标签: c++ language-lawyer c++03


【解决方案1】:

就标准而言,构造函数不负责设置 vtable。没有任何人负责设置 vtable;就标准而言,vtable 不存在。

相反,vtable 是编译器必须遵循的其他规则的结果,与虚函数绑定等有关。因此,无论是否调用构造函数,vtable 都会在某处 设置,否则编译器将无法履行其其他义务。这与值初始化规则并不矛盾;相反,它增加了实施规则的实用性的细微差别。

【讨论】:

  • 也许我不应该以 vptr 为例。我的观点是,跳过对非平凡构造函数的调用不会导致一些潜在的问题吗?毕竟,它被称为不平凡是有原因的。
  • 不,因为非平凡的默认构造函数所做的事情与值初始化所做的事情相同。
  • 引用标准:“A constructor is trivial if it is an implicitly-declared default constructor and if: - its class has no virtual functions (10.3) and no virtual base classes (10.1), and ...”。虽然添加一个虚函数会使隐式声明的默认构造函数从平凡变为不平凡,但它仍然做同样的事情(不多也不少),所以跳过它根本不会受到伤害。我理解正确吗?
  • 是的。琐碎/非琐碎定义确实 用于解决与 vtable 设置/拆卸相关的问题,但这种特殊处理非琐碎构造函数的特殊例外不会损害编译器执行其 vtable 的能力职责。
【解决方案2】:

当类X 没有用户提供的构造函数时,其默认构造函数的作用与X::X() {} 形式的构造函数完全相同(C++03[class.ctor]§6)。就标准而言,它被定义为执行所有成员和基类子对象的默认初始化,仅此而已。所以“调用生成的默认构造函数”等同于“默认初始化所有数据成员和基类子对象”。

所以这实际上比您引用的值初始化“少” - 因为该值初始化值初始化所有数据成员和基类子对象。所以它完成了构造函数所做的所有事情,甚至更多。

就特定于实现的东西(如 vtable 指针)而言,这些都超出了标准的范围。编译器有责任确保其所有特定于实现的机制都能正常工作,而不管标准要求的构造函数调用如何。

【讨论】:

  • 粗体部分表示without an initializer,但 value-initialize 确实有一个初始化器:()。至于vptr,也许我不应该以vptr为例。我的观点是,跳过对非平凡构造函数的调用不会对对象的有效性造成一些潜在的问题吗?毕竟,它被称为不平凡是有原因的。
  • 引用标准:“A constructor is trivial if it is an implicitly-declared default constructor and if: - its class has no virtual functions (10.3) and no virtual base classes (10.1), and ...”。虽然添加一个虚函数会使隐式声明的默认构造函数从平凡变为不平凡,但它仍然做同样的事情,所以跳过它根本不会受到伤害。我理解正确吗?
猜你喜欢
  • 1970-01-01
  • 2012-08-16
  • 2023-03-09
  • 1970-01-01
  • 2013-04-01
  • 2018-06-30
  • 2013-02-15
  • 2020-05-11
  • 2014-07-15
相关资源
最近更新 更多