Pio*_*ycz 4 c++ multiple-inheritance virtual-inheritance diamond-problem copy-and-swap
考虑经典的虚拟继承钻石层次结构.我想知道在这种层次结构中复制和交换习语的正确实现是什么.
这个例子有点人为 - 并且它不是很聪明 - 因为它可以很好地使用A,B,D类的默认复制语义.但只是为了说明问题 - 请忘记示例弱点并提供解决方案.
所以我从两个基类(B <1>,B <2>)派生出D类 - 每个B类几乎从A类继承.每个类都有非平凡的复制语义,使用复制和交换习语.派生最多的D类在使用这个习语时有问题.当它调用B <1>和B <2>交换方法时 - 它将虚拟基类成员交换两次 - 所以一个子对象保持不变!
A:
class A {
public:
A(const char* s) : s(s) {}
A(const A& o) : s(o.s) {}
A& operator = (A o)
{
swap(o);
return *this;
}
virtual ~A() {}
void swap(A& o)
{
s.swap(o.s);
}
friend std::ostream& operator << (std::ostream& os, const A& a) { return os << a.s; }
private:
S s;
};
Run Code Online (Sandbox Code Playgroud)
乙
template <int N>
class B : public virtual A {
public:
B(const char* sA, const char* s) : A(sA), s(s) {}
B(const B& o) : A(o), s(o.s) {}
B& operator = (B o)
{
swap(o);
return *this;
}
virtual ~B() {}
void swap(B& o)
{
A::swap(o);
s.swap(o.s);
}
friend std::ostream& operator << (std::ostream& os, const B& b)
{ return os << (const A&)b << ',' << b.s; }
private:
S s;
};
Run Code Online (Sandbox Code Playgroud)
d:
class D : public B<1>, public B<2> {
public:
D(const char* sA, const char* sB1, const char* sB2, const char* s)
: A(sA), B<1>(sA, sB1), B<2>(sA, sB2), s(s)
{}
D(const D& o) : A(o), B<1>(o), B<2>(o), s(o.s) {}
D& operator = (D o)
{
swap(o);
return *this;
}
virtual ~D() {}
void swap(D& o)
{
B<1>::swap(o); // calls A::swap(o); A::s changed to o.s
B<2>::swap(o); // calls A::swap(o); A::s returned to original value...
s.swap(o.s);
}
friend std::ostream& operator << (std::ostream& os, const D& d)
{
// prints A::s twice...
return os
<< (const B<1>&)d << ','
<< (const B<2>&)d << ','
<< d.s;
}
private:
S s;
};
Run Code Online (Sandbox Code Playgroud)
S 只是一个存储字符串的类.
在进行复制时,您会看到A :: s保持不变:
int main() {
D x("ax", "b1x", "b2x", "x");
D y("ay", "b1y", "b2y", "y");
std::cout << x << "\n" << y << "\n";
x = y;
std::cout << x << "\n" << y << "\n";
}
Run Code Online (Sandbox Code Playgroud)
结果是:
ax,b1x,ax,b2x,x
ay,b1y,ay,b2y,y
ax,b1y,ax,b2y,y
ay,b1y,ay,b2y,y
Run Code Online (Sandbox Code Playgroud)
可能添加B<N>::swapOnlyMe会解决问题:
void B<N>::swapOnlyMe(B<N>& b) { std::swap(s, b.s); }
void D::swap(D& d) { A::swap(d); B<1>::swapOnlyMe((B<1>&)d); B<2>::swapOnlyMe((B<2>&)d); ... }
Run Code Online (Sandbox Code Playgroud)
但是当B从A私下继承时呢?
这是一个哲学咆哮:
我不认为虚拟继承可以或应该是私有的.虚拟基础的整个要点是,派生程度最高的类拥有虚拟基础,而不是中间类.因此,不应允许任何中间类"占用"虚拟基础.
让我重复一点:最派生的类拥有虚拟基础.这在构造函数初始化器中很明显:
D::D() : A(), B(), C() { }
// ^^^^
// D calls the virtual base constructor!
Run Code Online (Sandbox Code Playgroud)
在同样的意义上,所有其他操作D应该立即负责A.因此,我们自然会像这样编写派生交换函数:
void D::swap(D & rhs)
{
A::swap(rhs); // D calls this directly!
B::swap(rhs);
C::swap(rhs);
// swap members
}
Run Code Online (Sandbox Code Playgroud)把所有这些放在一起,我们只剩下一个可能的结论:你必须编写中间类的交换函数而不需要交换基数:
void B::swap(B & rhs)
{
// swap members only!
}
void C::swap(C & rhs)
{
// swap members only!
}
Run Code Online (Sandbox Code Playgroud)现在你问,"如果其他人想要从中得到D什么呢?现在我们看到Scott Meyer的建议总是使非叶类抽象的原因:遵循这个建议,你只实现swap调用虚拟基础交换的最终函数混凝土,叶子类.
更新:这里只是切向相关的东西:虚拟交换.我们继续假设所有非叶类都是抽象的.首先,我们将以下"虚拟交换功能"放入每个基类(虚拟或非虚拟):
struct A
{
virtual void vswap(A &) = 0;
// ...
};
Run Code Online (Sandbox Code Playgroud)
此功能的使用当然仅保留给相同类型.这是由隐式异常保护的:
struct D : /* inherit */
{
virtual void vswap(A & rhs) { swap(dynamic_cast<D &>(rhs)); }
// rest as before
};
Run Code Online (Sandbox Code Playgroud)
这种方法的整体效用是有限的,但如果我们碰巧知道它们是相同的,它确实允许我们以多态方式交换对象:
std::unique_ptr<A> p1 = make_unique<D>(), p2 = make_unique<D>();
p1->vswap(*p2);
Run Code Online (Sandbox Code Playgroud)