什么是交换适当的方式和副本成语在虚拟继承?(What is the proper approach

2019-09-22 05:03发布

考虑经典的虚拟继承钻石层次。 我想知道什么是在这样的层次结构中的正确实施的复制和交换成语。

该例子是有点做作-这是不是很聪明-因为这将起到很好的与A,B,d类语义默认副本。 但只是为了说明问题 - 请忘掉例如弱点和提供解决方案。

所以,我有类d 2基类(B <1>,B <2>)衍生的 - 每个B类的继承几乎从A类。 每个类都有使用复制和交换成语的非平凡的拷贝语义。 最派生d类与使用这种风格的问题。 当它调用乙<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;
};

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;
};

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;
};

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";
}

其结果是:

ax,b1x,ax,b2x,x
ay,b1y,ay,b2y,y
ax,b1y,ax,b2y,y
ay,b1y,ay,b2y,y

可能加入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); ... }

但是,当B从A继承私下?

Answer 1:

这里是一个哲学的咆哮:

  1. 我不认为虚拟继承可以或应该是私有的。 虚拟碱的整个的一点是, 最派生类拥有虚拟的基础上,而不是在中间的类。 因此,没有中间类应该被允许“猪”的虚拟基础。

  2. 让我重复一点:最派生类拥有虚拟基础。 这在构造函数初始化是显而易见的:

     D::D() : A(), B(), C() { } // ^^^^ // D calls the virtual base constructor! 

    在同样的意义,在所有其他操作D ,应立即负责A 。 因此,我们自然会导致写这样的衍生交换功能:

     void D::swap(D & rhs) { A::swap(rhs); // D calls this directly! B::swap(rhs); C::swap(rhs); // swap members } 
  3. 把这个都在一起,我们只剩下一个可能的结论:你必须写中间阶级的交换功能,而无需交换的基础:

     void B::swap(B & rhs) { // swap members only! } void C::swap(C & rhs) { // swap members only! } 

现在你问,“如果别人希望从推导出D现在我们看到斯科特·迈耶的意见,总是让非叶类抽象的理由:按照他的意见,你只需要实现最终swap它调用虚基交换功能在混凝土中,叶类。


更新:这里的东西只是一带而相关:虚拟交换。 我们继续假设所有非叶类是抽象的。 首先,我们把下面的“虚拟交换功能”到每一个基类(虚拟的或没有):

struct A
{
    virtual void vswap(A &) = 0;
    // ...
};

这个功能的用法当然被保留只相同的类型。 这是通过一个隐含的例外保障:

struct D : /* inherit */
{
    virtual void vswap(A & rhs) { swap(dynamic_cast<D &>(rhs)); }

    // rest as before
};

这样做的整体效用是有限的,但它确实让我们交换对象多态,如果我们碰巧知道它们是相同的:

std::unique_ptr<A> p1 = make_unique<D>(), p2 = make_unique<D>();
p1->vswap(*p2);


Answer 2:

虚拟基通常是指最派生类对象的是在它的控制。

第一个解决方案:重新组织你的类更适合多态性。 制作拷贝构造的保护。 删除分配和swap() 添加虚拟clone() 想法是,类应该是多态的处理。 因此,他们应该与指针或智能指针使用。 交换或分配应该是指针值,而不是该对象的值。 在这样的背景下交换和分配只会混淆。

解决方法二:使B和C的抽象和它们的指针不是管理对象的生命周期。 B和C的析构函数应该被保护和非虚拟的。 B和C将不会因此是对象的最派生类。 让B::swap()C::swap()的保护,而不是交换子对象,可以重命名或添加评论,这是继承类的业务了。 去除大量的对象切片的可能性。 让D::swap()来交换子对象。 你得到A的一个交换

第三个解决办法: D::swap()来交换子对象。 这样子对象将在正确的位置进行交换3次和土地的一个。 效率低下? 整个构造可能是不好的想法。 我如不知道如何很好的虚析构函数和互换合作,在这里和很多办法切片对象是公众在这里。 这一切似乎类似于试图使虚拟赋值运算符是坏主意在C ++的。

如果某样东西从它的量级d继承,那么它应该确保通过交换或者不交换子对象是交换A的数量为奇数。 它成为控制因此应接管和修复。

private virtual成语是的方式,使一类决赛在C ++之一。 没有什么应该能够从它继承。 有趣的是,你问。 如果你使用它,一定要发表评论,它混淆了大部分的代码的读者。



文章来源: What is the proper approach to swap and copy idiom in virtual inheritance?