【问题标题】:Virtual inheritance and uniform initialization in C++C++ 中的虚拟继承和统一初始化
【发布时间】:2015-11-09 00:27:58
【问题描述】:

跟进this question about multiple (virtual) inheritance,我想询问一个简单的 MWE,它让 g++ 5.2.0 不安,而 clang++ 3.6.2 处理得很好,完全没有抱怨,即使设置了-Wall-Wextra。这是 MWE:

class Z {};
class A : virtual Z { protected: A() {} };
class B : virtual Z { protected: B() {} };
class C : A, B { public: C() : A{}, B{} {} };
int main() { C c{}; return 0; }

与 clang++ 不同,g++ 的抱怨如下:

gccodd.c++: In constructor ‘C::C()’:
gccodd.c++:2:34: error: ‘A::A()’ is protected
 class A : virtual Z { protected: A() {} };
                                  ^
gccodd.c++:4:39: error: within this context
 class C : A, B { public: C() : A{}, B{} {} };
                                       ^
gccodd.c++:3:34: error: ‘B::B()’ is protected
 class B : virtual Z { protected: B() {} };
                                  ^
gccodd.c++:4:39: error: within this context
 class C : A, B { public: C() : A{}, B{} {} };
                                       ^

用旧形式替换 C 构造函数中的统一初始化虽然效果很好,但 clang++ 和 g++ 都对以下内容感到满意:

class C : A, B { public: C() : A(), B() {} };

这产生了两个明显的选择:

  1. 代码在某种程度上违反了标准,导致结果未定义(即任何结果都是可以接受的)。
  2. 两个编译器之一存在与统一初始化和多重 + 虚拟继承相关的错误。

如果是投票问题,(1) 可能会获胜,因为 icpc 15.0.0 说明如下:

gccodd.c++(4): error #453: protected function "A::A()" (declared at line 2) is not accessible through a "A" pointer or object
  class C : public virtual A, public virtual B { public: C() : A{}, B{} {} };
                                                                ^

gccodd.c++(4): error #453: protected function "B::B()" (declared at line 3) is not accessible through a "B" pointer or object
  class C : public virtual A, public virtual B { public: C() : A{}, B{} {} };
                                                                     ^

那么,是(1)还是(2)?如果是前一种情况,那我的 MWE 有什么问题?

【问题讨论】:

  • 听起来像一个错误。没有理由认为这里会有 UB。
  • VC++ 14.0 也可以很好地编译它,尽管 IntelliSense 会抱怨它。
  • @0x499602D2 有趣的是,它看起来像是在尝试制作一个临时文件,然后进行复制。这也可以解释 OP 的错误。

标签: c++ c++11 inheritance gcc c++14


【解决方案1】:

T 类型的对象或引用的列表初始化定义为 如下:
(3.1) — 如果T 是一个类类型并且初始化列表有一个 cv 类型的单个元素 U [..]
(3.2) — 否则,如果 T 是 字符数组 [..]
(3.3) — 否则,如果 T 是一个聚合, 执行聚合初始化(8.5.1)。
(3.4) — 否则,如果 初始化器列表没有元素,T 是一个类类型 默认构造函数,对象是值初始化的。

AB 都有基类,因此不是聚合。所以第四点适用。因此,我们的效果与使用() 完全相同:

一个对象,其初始值设定项是一组空括号,即, (),应进行值初始化。

使用这些初始化程序产生不同结果的任何编译器都不能符合要求。

§11.4 处理对protected 成员的访问,没有提及与初始化形式相关的任何内容。但是,关于构造函数中 mem-initializer 中基的初始化,正如 CWG 问题 #1883 所述,§11.4 目前存在缺陷。

【讨论】:

  • 感谢您指出描述此案例的标准的确切部分。因此,英特尔编译器/“作曲家”似乎过于重视其 GCC 兼容性,也实现了 GCC 错误。 :-) 至于标准中的错误,我最近发现了this question about references and uniform initialization。基本上,标准中 2 个要点的换位打开了 3 年的错误(在某种意义上)编译器窗口。
猜你喜欢
  • 2012-08-31
  • 2014-11-23
  • 2016-11-24
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 2021-07-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多