使用C中多重继承的派生类的vtable为其中一个基类调用赋值运算符

好吧,这有点棘手.这是一个(简化的)代码:

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)
{
}
点赞