【问题标题】:Copy-constructor of multiply-inherited classes多重继承类的复制构造函数
【发布时间】:2012-05-26 12:50:04
【问题描述】:

考虑这段代码:

#include <vector>
#include <iostream>
using namespace std;

class Base
{
    char _type;
public:
    Base(char type):
        _type(type)
    {}

    ~Base() {
        cout << "Base destructor: " << _type << endl;
    }
};

class uncopyable
{
    protected:
        uncopyable() {}
        ~uncopyable() {}
    private:
        uncopyable( const uncopyable& );
        const uncopyable& operator=( const uncopyable& );
};

class Child : public Base, private uncopyable
{
    int j;
public:
    Child():
        Base('c')
    {}
    ~Child() {
        cout << "Child destructor" << endl;
    }
};


int main()
{
    vector<Base> v;
    Base b('b');
    Child c;

    v.push_back(b);
    v.push_back(c);
    return 0;
}

我系统上的输出是:

Base destructor: b
Child destructor
Base destructor: c
Base destructor: b
Base destructor: b
Base destructor: c

我的问题是:

  • 为什么Base(类型为b)的析构函数被调用了三次而不是两次(我们是否有两个以上的对象b副本)?

  • 当我们复制 Child 类型的对象时会发生什么,考虑到其父对象之一的复制构造函数是私有的。是未定义的行为吗?

  • 当我尝试复制 Child 类型的对象时,我预计会出现编译时错误。我以为孩子的默认复制构造函数会尝试调用 Uncopyable 类的私有复制构造函数并导致编译错误。为什么不给出编译错误?

之所以这样设计代码是因为Child 类很大。

当客户端试图复制Child 对象时,期望的行为是丢弃子数据(调用Child 的析构函数而不调用Base 的析构函数)。

这段代码实现了这一点,但我猜它会导致未定义的行为并存在内存泄漏(从不为复制的实例调用 Child 的析构函数)。

【问题讨论】:

  • 您想要compile-time 错误还是要丢弃Child 的数据?这些是相互矛盾的陈述,除非我遗漏了什么。

标签: c++ inheritance stl multiple-inheritance copy-constructor


【解决方案1】:

编辑:答案是错误的。目前正在编辑以得到更好的回应。

为什么 Base(类型 b)的析构函数被调用了 3 次而不是 2 次(我们是否有超过两个对象 b 的副本)?

很可能是 Vector 正在复制 b。向量经常这样做。

当我们复制一个 Child 类型的对象时会发生什么,考虑到其父对象之一的复制构造函数是私有的。是未定义的行为吗?

没有。 C 的复制构造函数会调用基类的复制构造函数。所以如果基类拷贝构造函数是私有的,它就不会编译。

当我尝试复制 Child 类型的对象时,我需要得到一个编译时错误,同时允许复制基类对象。最好的方法是什么?

像这样为 Child 声明一个私有复制构造函数:

private:
    Child(const Child& a) {
        throw "cannot make a copy";
    } 

当客户端试图复制 Child 对象时,期望的行为是丢弃子数据(调用 Child 的析构函数而不调用 Base 的析构函数)。

不确定你的意思。复制构造函数意味着创建一个新对象。您不能对(旧)对象进行操作。

【讨论】:

  • 1.恕我直言向量应该复制一次。不是两次。 2.恕我直言,孩子的隐式复制构造函数应该调用父母的复制构造函数。 stackoverflow.com/questions/9178204/… 3. 不行(可以试试)。因为对象首先被类型转换为父对象,然后被复制。
  • 在建议的代码中,我们正在创建一个新的 Child,而不是复制数据。它违背了不再创建新对象的目的。
  • 我想我错过了一些东西。当您从另一个对象创建对象时,将调用复制构造函数。这就是复制构造函数的作用......你能解释一下你想要做什么吗?
  • 其实你是对的..我会编辑答案..我将不得不回到这个。
【解决方案2】:

每当我尝试复制 Child 类型的对象时,我都希望得到一个编译时错误。

您不是在复制 Child 对象。当您将Child c 放入vector&lt;Base&gt; 时,只会复制Base。与执行b = c;基本相同。如果你复制/分配Child,你会得到一个错误。

Child d = c;  // compile error

默认复制构造函数将调用任何基类和成员对象的复制构造函数,并对基元和指针进行按位复制。

【讨论】:

  • 谢谢。这似乎是正确的。对 Base 的析构函数的额外调用呢?
  • @Shayan,它可能是在重新分配其存储空间时生成的额外复制向量。如果过度分配,不同的向量 impls 的行为可能会有所不同。
【解决方案3】:

这是您的代码中发生的情况:

int main() 
{ 
    vector<Base> v;    // 1
    Base b('b');       // 2
    Child c;           // 3

    v.push_back(b);    // 4
    v.push_back(c);    // 5
    return 0; 
}                      // 6
  1. 第 1 行:向量 v 构造

  2. 第 2 行:构造了 Base b(调用 Base 的构造函数)

  3. 第 3 行:Child c 构造(调用 Child 的构造函数和 Base 的构造函数)

  4. 第 4 行:v 当前处于最大容量,需要调整大小。
    v 为 Base 的 1 个元素分配内存。
    Base b 复制到 v[0] 中(调用 Base 的复制构造函数)。

  5. 第 5 行:v 再次达到最大容量,需要调整大小。
    v 为 Base 的 2 个元素分配了内存。
    旧的 v[0] 被复制到新的 v[0] 中(调用 Base 的复制构造函数)。
    旧的 v[0] 被删除(调用 Base 的析构函数(“Base destructor: b”))。
    子 c 被复制到 v[1] 中(调用 Base 的复制构造函数)。

  6. 第 6 行:c、b 和 v 超出范围。
    子 c 被删除(调用 Child 的析构函数(“Child destructor”)然后调用 Base 的析构函数(“Base destructor: c”)。
    Base b 被删除(调用 Base 的析构函数 ("Base destructor: b"))。
    Base v[0], v[1] 被删除(调用 Base 的析构函数两次(“Base destructor: b”, “Base destructor: c”))。

没有内存泄漏 - 对于上述序列中的每个构造函数,都会调用相应的析构函数。

此外,您似乎对复制构造函数感到非常困惑。 Child c 作为 Base& 传递给 push_back - 然后按预期调用 Base 的复制构造函数。由于 Base 的隐式复制构造函数不是虚拟的或被覆盖的,因此让 Child 从 uncopyable 派生不会改变这一点。

请注意,vector&lt;Base&gt; 永远不能存储 Child 类型的对象;它只知道为 Base 分配足够的内存。将 Child 的实例分配给 Base 时发生的事情称为切片,虽然这通常是无意和误解的,但看起来它实际上可能是您所描述的场景中想要的。

【讨论】:

猜你喜欢
  • 2012-02-17
  • 1970-01-01
  • 2015-05-25
  • 1970-01-01
  • 2014-08-21
  • 1970-01-01
  • 2015-04-11
相关资源
最近更新 更多