虚拟继承,默认构造函数和额外复制

时间:2015-03-21 11:55:05

标签: c++ inheritance virtual-inheritance

如果您查看下面的代码,您会看到有一些类ABC,它们都是从上一个继承的。继承是virtual,这意味着C必须调用A的构造函数,即使它不直接从A继承。

这一切都很好,但是有两种方法可以解决这个问题,而且它们对我来说都很难看,所以我想知道这是否真的是你应该怎么做。

第一种方式(下面的代码原样)似乎两次拨打A::A(a),一次在B的构造函数中,然后再次在C'的构造函数。当然它没有被构造两次,但是副作用是参数a被复制一个额外的时间,只是因为它可以传递给B::B(a, b),即使该函数没有& #39; t最终使用该值。它只需要构造A::A(a)的值,但是这里构造函数是从C调用的,因此传递给a的{​​{1}}的副本将被丢弃。

为了避免这种浪费的副本,另一种方法是在基类中创建其他构造函数(为此选项取消注释下面的代码。)这样可以避免浪费的副本,但缺点是你作为程序员正在创建一个全新的类的构造函数,在运行时从未实际调用过代码。您必须声明该函数,实现它(即使它是空的)并实际编写调用它的代码,即使该代码永远不会实际运行!这种事情通常会在某处发出破碎的设计信号,所以我想知道是否还有另一种方法可以实现这种不那么狡猾的设计。

B::B(a, b)

编辑:为了澄清,有几个人发表评论说我应该通过引用来传递。忽略这并不总是你想要的事实(想想聪明的指针),它并没有消除你将一个值传递给一个永远不会被使用的函数的事实,这似乎很狡猾。因此,选择要么传递一个静默抛弃的值(希望你永远不会被它咬掉),要么实现一个你永远不会打电话的函数(希望永远不会再回来咬它。)

所以我原来的问题仍然存在 - 这些真的只有两种方法吗?你必须在一个多余的变量或多余的函数之间做出选择?

2 个答案:

答案 0 :(得分:2)

我不确定,如果这是你的意图,但你正在做很多不必要的Data副本,因为每个参数都是按值传递的。这意味着,每次调用A::A(a)时,都会创建Data的新实例,该实例是从a参数复制构造的。在B::B(a,b)中,制作了两份此类副本,并在C::C(a,b,c)中制作了三份副本。但请注意,B::B(a,b)调用A::A(a)C::C(a,b,c)来电B::B(a,b),我猜,调用C::C(a,b,c)实际上会创建六个副本Data

  • a
  • 的三个副本
  • b
  • 的两个副本
  • c
  • 的一个副本

您可以通过引用传递构造函数的参数来避免这种情况 - 然后,不会复制,除非您在构造函数体或初始化列表中决定创建一个。

试试这个:

#include <iostream>

struct Data {
    Data() {
        std::cout << "Creating data\n";
    }
    Data(const Data& d) {
        std::cout << "Copying data\n";
    }
};

struct A {
    A(const Data& a)
    {
        std::cout << "A(a)\n";
    }
/*
    A()
    {
        std::cout << "A()\n";
    }
*/
};

struct B: virtual public A {
    B(const Data& a, const Data& b)
        :   A(a)
    {
        std::cout << "B(a, b)\n";
    }
/*
    B(Data b)
    {
        std::cout << "B(b)\n";
    }
*/
};

struct C: virtual public B {
    C(const Data& a, const Data& b, const Data& c)
        :   A(a),
            B(a, b)
/*
            B(b)
*/
    {
        std::cout << "C(a, b, c)\n";
    }
};

int main(void)
{
    Data a, b, c;
    std::cout << "-- B --\n";
    B bb(a, b);
    std::cout << "-- C --\n";
    C cc(a, b, c);

    return 0;
}

输出:

Creating data
Creating data
Creating data
-- B --
A(a)
B(a, b)
-- C --
A(a)
B(a, b)
C(a, b, c)

修改

我明白了,我误解了你的问题。现在,我不确定我是否理解它。编译原始代码后:

[输出]

[Cut]
-- C --
Copying data // copy of c in C::C()
Copying data // copy of b in C::C()
Copying data // copy of a in C::C()
Copying data // copy of a passed to A::A()
A(a)
Copying data // copy of b passed to B::B()
Copying data // copy of a passed to B::B()
B(a, b)
C(a, b, c)

现在,您要摆脱哪些副本?

您在A::A()B::B()写道,C::C()被调用了两次。但是您创建了两个对象,一个类型为B,另一个类型为C。这两种类型都继承自A,因此始终会调用A::A() - 无论是否明确。如果您未明确调用A::A(),则会调用A的默认构造函数。如果未定义,则会生成编译器错误(尝试从B(a, b)删除对C::C()的调用)。

答案 1 :(得分:1)

你使用虚拟继承的方式是错误的,在这个问题中 - &#34;额外的副本&#34;和虚拟继承

虚拟继承是为了解决钻石问题 - 如果马和鸟都继承了动物&#34;和Pegasus继承了马和鸟 - 我们不希望每个成员变量在&#34; Animal&#34;两次被包含。我们实际上继承了马和鸟,以便在Pegasus中只有一次Animal Object。

这里,这个例子没有呈现钻石问题,因此没有反映虚拟继承的任何实际用途(除此之外它强制首先调用一个ctor。)

另外,有这么多副本的原因是因为你按值传递所有内容而不是你想要的const引用。将所有内容作为参考文献传递将在这里避开大部分副本。