如果您查看下面的代码,您会看到有一些类A
,B
和C
,它们都是从上一个继承的。继承是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)
编辑:为了澄清,有几个人发表评论说我应该通过引用来传递。忽略这并不总是你想要的事实(想想聪明的指针),它并没有消除你将一个值传递给一个永远不会被使用的函数的事实,这似乎很狡猾。因此,选择要么传递一个静默抛弃的值(希望你永远不会被它咬掉),要么实现一个你永远不会打电话的函数(希望永远不会再回来咬它。)
所以我原来的问题仍然存在 - 这些真的只有两种方法吗?你必须在一个多余的变量或多余的函数之间做出选择?
答案 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引用。将所有内容作为参考文献传递将在这里避开大部分副本。