C++ 虚拟继承:在派生的初始值设定项列表中static_cast "this"虚拟父级
C++ Virtual Inheritance: static_cast "this" to virtual parent in initializer list of derived
我有一些代码。它不起作用。
首先,你会看这个示例代码片段并思考"为什么?"但相信我:这是有原因的。
代码如下:
class LinkedListNode
// blaa
{
public:
LinkedListNode ( void* p )
{
// blaa
}
} ;
template <typename T>
class InheritAndLinkList
: public virtual T
, public LinkedListNode
{
public:
InheritAndLinkList ()
: LinkedListNode ( static_cast<void*>(static_cast<T*>(this)) ) // an exception occurs here when ..... (scroll down)
{ }
} ;
template <typename T>
class Implements
: public virtual InheritAndLinkList<T>
{ } ;
class A
{
public:
virtual void goA () =0 ;
} ;
class B
: public Implements<A>
{
public:
virtual void goB () =0 ;
} ;
class MyClass
: public Implements<B>
{
public:
virtual void goA ()
{
// blaa
}
virtual void goB ()
{
// blaa
}
} ;
int main ( ... )
{
MyClass * p = new MyClass () ; // ..... This line executes
p->goA() ;
p->goB() ;
return 0 ;
}
具体的错误是,在构造时,表达式static_cast<T*>(this)
会导致分割错误......使用英特尔C++编译器时。这已经在GCC,LLVM,MS Visual Studio等的许多版本上工作了多年。而现在ICPC让它死了。
我相信这是一件完全有效的事情。当这条线被调用时,T
已经构造好了,应该可以使用...除非C++规范中还有其他奇怪的事情。
将static_cast
放在构造函数主体中(并更改其超级以匹配(会导致它避免这种段错误。
所以我的问题:规范中哪里说这个 [静态强制转换] 是/不安全的?
就其价值而言,代码对我来说看起来还可以。我认为这种static_cast
的使用没有任何争议 - 它是普通的派生到基础指针转换。对我来说看起来像一个编译器错误。
如果你坚持章节和经文:
[expr.static.cast]/4 对于某些发明的临时变量
t
(8.5(,可以使用形式static_cast
static_cast<T>(e)
的表达式e
T t(e);
显式转换为T
类型。此类显式转换的效果与执行声明和初始化,然后使用临时变量作为转换结果的效果相同。
因此,我们正在研究InheritAndLinkList<T>
构造函数中T t(this);
的有效性 - 直接初始化:
[dcl.init]/17 ...
-- 否则,正在初始化的对象的初始值是初始值设定项表达式的(可能已转换(值。如有必要,将使用标准转换(第 4 条(将初始值设定项表达式转换为目标类型的 cv 非限定版本;不考虑用户定义的转换。
.
[conv.ptr]/3 类型为 "指向 cv
D
的指针" 的 prvalue,其中D
是类类型,可以转换为 "pointer "类型的 prvalue 到 cvB
",其中B
是D
的基类(第 10 条(。如果B
是不可访问的(条款11(或不明确的(10.2(基类D
,那么需要这种转换的程序是格式不正确的。转换的结果是 指向派生类对象的基类子对象的指针。
编辑
经过评论中的激烈讨论,使用构造函数初始值设定项列表中的this
并不那么简单 - 但我相信您的特定用途仍然是合法的。
[class.cdtor]/3 要显式或隐式地将引用类
X
对象的指针(glvalue(转换为指向X
的直接或间接基类B
的指针(引用(,X
的构造及其直接或间接派生自B
的所有直接或间接基础的构造应该已经开始,并且这些类的销毁尚未完成, 否则,转换会导致未定义的行为...[示例:struct A { }; struct B : virtual A { }; struct C : B { }; struct D : virtual A { D(A*); }; struct X { X(A*); }; struct E : C, D, X { E() : D(this), // undefined: upcast from E* to A* // might use path E* ! D* ! A* // but D is not constructed // D((C*)this), // defined: // E* ! C* defined because E() has started // and C* ! A* defined because // C fully constructed X(this) { // defined: upon construction of X, // C/B/D/A sublattice is fully constructed } };
— 结束示例 ]
您的情况类似于上面示例中的X(this)
,实际上比这更简单,因为您只在层次结构中向上转换一个步骤,因此无需关注中间类。
这可能是一个编译器错误。
我减少并简化了代码示例:
struct ctor_takes_int
{
// dummy parameter needed to put expression in a ctor-init-list of derived class
ctor_takes_int (int=0){ }
} ;
struct stupid_base
{
//int nevermind;
} ;
struct upcast_in_init_list;
/*
* volatile = anti optimisation :
* no value propagation possible on volatile variables
* no constant propagation
* no inlining of volatile pointer to function!
*/
int (*volatile upcast) (struct upcast_in_init_list *that);
struct upcast_in_init_list
: virtual stupid_base, ctor_takes_int
{
upcast_in_init_list ()
: ctor_takes_int (upcast(this))
{ }
} ;
/*
* volatile = anti optimisation
* no dead assignment removal
*/
stupid_base *volatile p;
// must be compiled out of line
int do_upcast (upcast_in_init_list *that) {
p = that;
return 0;
}
int main ()
{
upcast = &do_upcast;
new upcast_in_init_list() ;
return 0 ;
}
程序在 http://www.tutorialspoint.com/compile_cpp11_online.php 崩溃
(请注意,使用 volatile
来防止某些优化,但在实践中似乎并不需要。它只是更"健壮",有一个"健壮的崩溃"。
如果我不是调用函数,而是在 ctor-init-list 中进行向上转换,则((p = this,0))
,程序可以工作。这意味着编译器知道如何在构造函数对象的 ctor-init-list 内this
上执行指针转换,但常见的转换代码不知道如何执行转换,因为派生对象此时不存在(例如,你不能使用它typeid
(。
当您从实现的角度考虑它时,这是可以理解的:对于非虚拟基类,派生到基指针的转换是一个简单的"如果非 null 添加固定偏移量"调整,但它涉及虚拟基类更复杂的事情,因为它们不驻留在固定偏移量,根据定义(使基类成为虚拟很像添加间接级别(。
虚拟项(虚函数、虚拟基类(的本质是对对象的动态(真实(类型的依赖。请注意,没有虚函数但具有虚拟基类的类在C++中不是"多态"的,并且不支持 RTTI(dynamic_cast
和 typeid
(,但仍必须具有一些"虚拟"运行时信息,vptr(vtable 指针(或一些虚拟基偏移量或指针。在任一情况下,运行时信息都会在构造期间初始化。
当进入构造函数的主体时(紧跟在{
之后(,正在构造的对象不会正式"存在">,因为它的生存期尚未开始:如果构造函数主体退出并出现异常,则不会调用相应的析构函数。但是未启动的生存期仍然具有"虚拟"对象的所有属性(= 具有虚拟功能的对象,无论是函数还是基类(。虚拟函数可以虚拟调用,并且将调用当前类中的覆盖器,typeid
将指示构造中对象的类型等。
在所有编译器中,向非虚拟基类的转换始终有效,因为不使用"虚拟"/动态信息,就像调用非虚拟函数"工作"(在实践中(在未构造的对象上一样,即使它不合法。
此外,初始化列表中this
表达式(不是值(的转换也有效,因为它是一种特殊的优化情况:编译器知道类的布局和完整构造函数(用于构造完整对象的构造函数,而不是基类子对象(中所有虚拟的(静态(偏移量。您可以看到this
是特殊情况:在完整构造函数的 ctor-init-list 中使用 (that = this, p = that, 0)
(其中that
是某个upcast_in_init_list *
变量(不起作用,因为不再识别特殊。
处理this
是一个基类构造函数调用(一个无法初始化虚拟基类的构造函数调用(显然也有效,我不知道为什么。
- 虚拟决赛作为安全
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- 为什么即使使用-cudart-static进行编译,库用户仍然需要链接到cuda运行时
- 如何在C++中获得"静态纯虚拟"功能?
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 如何处理 c++ 中类实现中的"invalid use of non-static data member"?
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 大小虚拟继承中的派生类
- 链接器找不到在虚拟类 c++ 中访问的静态字段的符号
- 使用 C++ 和 i2c 工具从虚拟 i2c 写入和读取
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- std::is_trivially_copyable_v 关于虚拟功能
- 删除C++继承中虚拟类成员的代码重复
- 子类地址等于虚拟基类地址?
- 从static中派生的基类实现一个虚拟方法