【问题标题】:Calling an assignment operator for one of bases with vtables of multiple-inherited derived class in C++在 C++ 中调用具有多个继承派生类的 vtable 的基之一的赋值运算符
【发布时间】:2015-11-08 11:11:48
【问题描述】:

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

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
}

对我来说,这符合“避免这样做”,但这是一个现有的代码,我们在此代码附近遇到了可疑的细微内存损坏。所以,我很好奇这样可以吗。

这里奇怪的是,两个基类都有 vtables,它们都没有任何显式的复制/赋值构造函数/操作符。

据我了解,Derived 类的内存布局如下

 `Derived`
 ---------
 A-vtable
 A-fields
 B-vtable
 B-fields

当我调用在“B”中声明的虚函数时,我使用的是B-vtable,当我调用在“A”中声明的虚函数时,我使用的是A-vtable,即vtables没有合并在一起。

据我了解,B 的隐式复制/赋值构造函数/运算符应该只影响B-fields,当它被称为*static_cast&lt;B*&gt;(this) = b; 时(static_cast 应该给出指向B-fields 开头的指针,B-vtable 驻留与它的负偏移量,AFAIK)。

所以,根据我的理解,这段代码是完全安全和正确的,虽然不清楚和hacky,但安全。我对么?是否有任何我应该注意的特定于编译器的怪癖(我们在这里谈论的是 MSVC 2012)?

编辑:伙计们,我知道复制构造函数/赋值运算符的区别,非常感谢。这是仅讨论复制构造函数的 3 次事件之一,因为我已经监督了它,现在每个答案都花费一半的文本来说明与问题差异完全无关的内容。

【问题讨论】:

  • “在这段代码附近出现了可疑的内存损坏” - 那就去提取一个最小的例子吧!
  • this 这样的指针通常指向存储vtable指针的隐藏字段(即指向对象的最开始,而不是指向它的偏移量),因为它经常使用。指针是否来自静态转换无关紧要。我认为它也可能是负偏移,这可能是一个实现细节。

标签: c++ multiple-inheritance


【解决方案1】:

是的,没错。将派生类强制转换为它的一个父类是一种特殊的行为。当发生多重继承时,就像你的情况一样,指针的实际地址可能会改变。要使用您的示例:

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&amp; other)。如果您没有定义一个,则使用默认的赋值运算符。等号仅在变量声明的上下文中被视为复制构造函数:

B newObj = b;

如果您想为 Derived 实现自己的复制构造函数,然后显式调用 B 的父复制构造函数,请尝试以下操作:

Derived::Derived(const B& b) : B(b)
{
}

【讨论】:

    【解决方案2】:

    为什么不直接调用基类的构造函数呢?

    Derived::Derived(const B& b)
    : B(b)
    {
    }
    

    赋值运算符也可以做类似的事情:

    Deriver& Derived::operator=(const Derived& rhs)
    {
        A::operator=(rhs); //Assign A's part
        B::operator=(rhs); //Assign B's part
        //Derived-specific assignments
    
        return *this;
    }
    

    这是初始化类对象的首选、安全且完全有效的方法,这些对象继承自某些东西。

    另外,在这一行:

    *static_cast<B*>(this) = b;
    

    您调用赋值运算符,而不是复制构造函数。既然你写了,这两种基类型都没有定义,使用默认(由编译器生成)。

    我不明白你为什么还要考虑 vtables。这是从具有编译器生成的复制构造函数和赋值运算符的类的简单继承。一切都很简单,无需任何技巧即可完成:)

    【讨论】:

    • 这不是我的代码,我通常完全避免继承,抛开多重继承。我知道还有其他方法可以在没有黑客的情况下做到这一点,但问题不在于那个。问题是关于 - 这种方式是否安全。但无论如何,谢谢,我想知道调用一个完全合格的操作员是否会做同样的事情。
    猜你喜欢
    • 2012-02-12
    • 2012-02-10
    • 2013-05-22
    • 2014-10-27
    • 2015-02-09
    • 2016-07-10
    • 2018-05-09
    • 1970-01-01
    • 2013-03-03
    相关资源
    最近更新 更多