好吧,这有点棘手.这是一个(简化的)代码:
class A
{
virtual ~A();
// fields, none of which has an assignment operator or copy constructor
};
class B
{
virtual ~B();
// same as A
};
class Derived : public A, public B
{
Derived();
Derived(const B& b);
// no fields
};
使用Derived :: Derived(const B& b)(即接受其中一个基数)如下
Derived::Derived(const B& b)
{
*static_cast<B*>(this) = b;
// Do other stuff with protected fields declared in B
}
对我来说,这是“只是避免这样做”,但这是一个现有的代码,我们在这段代码附近可疑地经历了一个微妙的内存损坏.所以,我很好奇,如果那没关系.
这里的好奇部分是两个基类都有vtable,并且它们都没有任何显式的复制/赋值构造函数/运算符.
根据我的理解,Derived类的内存布局如下
`Derived`
---------
A-vtable
A-fields
B-vtable
B-fields
当我调用一个虚函数时,在’B’中声明我正在使用B-vtable并且当我调用一个虚函数时,在’A’中声明我使用的是A-vtable,即vtable没有合并一起.
根据我的理解,B的隐式复制/赋值构造函数/运算符只应影响B字段,当它被称为* static_cast< B *>(this)= b; (static_cast应该给出一个指向B字段开始的指针,其中B-vtable位于与它的负偏移处,AFAIK).
所以,根据我的理解,这段代码是完全安全和正确的,虽然不清楚和hacky,但安全.我对么?是否有任何特定于编译器的怪癖应该注意(我们在这里讨论的是MSVC 2012)?
编辑:伙计们,我知道复制构造函数/赋值运算符的区别,非常感谢你.这是关于复制构造函数的三次出现之一,因为我已经监督了它,现在每个答案花费一半的文本来告诉与问题差异完全无关.
最佳答案 是的,这是正确的.将派生类强制转换为其父类之一就有这种特殊的行为.当发生多重继承时,就像你的情况一样,指针的实际地址可以改变.使用你的例子:
this -> | A-vtable |
| A-fields |
static_cast<B*>(this) -> | B-vtable |
| B-fields |
当您在Derived对象上调用从B派生的函数时,会发生相同的指针更改.
但是,请注意,复制构造函数不是您在行中调用的内容:
*static_cast<B*>(this) = b;
相反,您正在调用B的赋值运算符,即B :: operator =(const B& other).如果未定义一个,则使用默认赋值运算符.仅在变量声明的上下文中将等号视为复制构造函数:
B newObj = b;
如果要为Derived实现自己的复制构造函数,然后显式调用B的父复制构造函数,请尝试以下操作:
Derived::Derived(const B& b) : B(b)
{
}