【问题标题】:copy and swap idiom with pure virtual class使用纯虚拟类复制和交换习语
【发布时间】:2014-06-23 04:56:03
【问题描述】:

我正在尝试使用纯虚拟方法和“复制和交换”习语来实现虚拟类,但遇到了一些问题。代码无法编译,因为我在包含纯虚方法的 A 类的赋值运算符中创建实例。

有没有办法使用纯虚方法和复制和交换习语?

class A
{
public:
    A( string name) :
            m_name(name) { m_type = ""; }
    A( const A & rec) :
            m_name(rec.m_name), m_type(rec.m_type) {}
    friend void swap(A & lhs, A & rhs)
    {
        std::swap(lhs.m_name, rhs.m_name);
        std::swap(lhs.m_type, rhs.m_type);
    }

    A & operator=( const A & rhs)
    {
        A tmp(rhs); 
        swap(*this, tmp);
        return *this;
    }

    friend ostream & operator<<( ostream & os,A & x)
    {
         x.print(os);
         return os;
    }

protected:
    virtual void print(ostream & os) =0;    

    string m_type;
    string m_name;
};

class B : A
{
public:
    B(string name, int att) :
        A(name),
        m_att(att)
        {
            m_type="B";
        }

    B( const B & rec) :
        A(rec),
        m_att(rec.m_att) {}

    friend void swap(B & lhs, B & rhs)
    {
        std::swap(lhs.m_att, rhs.m_att);
    }

    B & operator=( const B & rec)
    {
        B tmp(rec) ;
        swap(*this, tmp);
        return *this;
    }

private:
    virtual void print(ostream & os);

    int m_att;

};

错误信息:

In member function ‘A& A::operator=(const A&)’:|
error: cannot declare variable ‘tmp’ to be of abstract type ‘A’|
because the following virtual functions are pure within ‘A’:|
virtual void A::print(std::ostream&)|

【问题讨论】:

  • 您不能拥有A tmp(rhs);A 是一个抽象类。
  • @MohitJain 我认为他很清楚这一点。这就是为什么他要问如何解决这个问题。
  • 也许this answer 也有帮助
  • 复制和交换习语不应与虚函数/类层次结构结合使用。这会导致切片(和 UB)和缓慢的实现(因为您的临时复制对象创建了一个完整的虚函数表)。相反,如果您想使用虚拟基类进行复制/分配,请将您的实现基于clone function with covariant return type
  • 顺便说一句:如果你执行 A & operator=( A rhs) { swap(*this, rhs);返回*这个; }

标签: c++ class virtual copy-and-swap


【解决方案1】:

正如您的编译器通知您的那样,您不能创建抽象类型的变量。没有办法绕着它跳舞。

这留下了三个主要选择:

停止使用纯虚函数

首先,你可以去掉纯虚方法,并在每个调用std::terminate的方法中提供一个小存根,这显然会破坏编译时检测是否所有(以前的)纯虚方法都被覆盖派生类。

这将导致slicing,因为它只会复制基类,而构成派生类的所有内容都会丢失。

使用没有纯虚函数的存根类

与此类似,您可以创建一个派生类,该类使用简单的存根(可能调用std::terminate)实现所有虚拟方法,并且仅用作“基类的可实例化版本”。

为这个类实现的最重要的部分是一个构造函数,它接受一个对基类的 const 引用,所以你可以直接使用它而不是复制基类。这个例子还添加了一个移动构造函数,因为我是一个性能迷。

这会导致与第一个选项相同的slicing 问题。根据您的操作,这可能是您的预期结果。

struct InstantiatableA : public A {
    InstantiatableA(A const& rhs) : A(rhs) { }
    InstantiatableA(A&& rhs) : A(::std::move(rhs)) { }

    void print(ostream&) override { ::std::terminate(); }
};

A& A::operator=(InstantiatableA rhs) {
    using ::std::swap;
    swap(*this, rhs);
    return *this;
}

注意:这实际上是一个A 类型的变量,虽然我说过它做不到。唯一需要注意的是A 类型的变量存在于InstantiatableA 类型的变量中!

使用复制工厂

最后,您可以将virtual A* copy() = 0; 添加到基类中。然后,您的派生类 B 必须将其实现为 A* copy() override { return new B(*this); }。之所以需要动态内存,是因为派生类型可能需要比基类更多的内存。

【讨论】:

    【解决方案2】:

    编译器是对的。 A 类是抽象类,因此不能在 operator= 中创建它的实例。

    在 B 中,您刚刚声明了 print 函数,但您没有实现它。意思是,你会得到链接错误。

    通过实现它,它编译得很好(如果我们忽略各种警告):

    void B::print(ostream & os )
    {
      os << m_att;
    }
    

    顺便说一句:

    • B 私有地从 A 继承,这是你想要的吗?
    • A 的复制构造函数中的初始化顺序错误
    • 您在 A 的构造函数主体中初始化了 m_type,而不是在初始化列表中

    【讨论】:

      【解决方案3】:

      您只是面临这样一个事实,即继承与复制语义一起工作很尴尬。

      例如,假设您找到了通​​过编译阶段的技巧,这意味着什么(以下示例使用赋值,但问题与副本相同):

      // class A
      // a class B : public A
      // another class C : public A inheriting publicly from A
      // another class D : public B inheriting publicly from B
      B b1;
      C c1;
      D d1;
      
      // Which semantic for following valid construction when copy/assignment is defined in A ?
      b1 = c1;
      b1 = d1;
      
      A &ra = b1;
      B b2;
      
      // Which semantic for following valid construction when copy/assignment is defined in A ?
      ra = b2;
      ra = c1;
      ra = d1;
      

      【讨论】:

      • 这并不能真正回答 OP 的问题。
      • @lethal-guitar :嗯,我想我知道。这是因为在我看来,如果不产生大的设计问题,就无法正确回答这个问题。因此,它通过说最初的问题是希望实现实体类型的复制和交换来回答这个问题。
      • 我明白了,您可以考虑将其编辑到您的答案中以使其更清晰。
      【解决方案4】:

      CRTP 是一种选择:

      template<typename swappable>
      struct copy_swap_crtp{
          auto& operator=(copy_swap_crtp const& rhs){
               if (this==std::addressof(tmp))
                  return *this;
               swappable tmp{rhs.self()};
               self().swap(tmp);
               return *this;
          };
          auto& operator=(copy_swap_crtp && rhs){
               self().swap(rhs.self());
               return *this;
          };
      protected:
          auto& self(){return *static_cast<swappable*>(this);};
          //auto& self()const{return *static_cast<swappable const*>(this);};
      };
      

      用户类别:

      struct copy_swap_class
      : copy_swap_crtp<copy_swap_class>
      {
          copy_swap_class(copy_swap_class const&);
          void swap(copy_swap_class&);
      };
      

      干杯, 调频。

      【讨论】:

        猜你喜欢
        • 2011-10-28
        • 2012-02-09
        • 2023-03-23
        • 1970-01-01
        • 1970-01-01
        • 2015-08-04
        • 2015-07-21
        • 2011-11-22
        • 2016-02-17
        相关资源
        最近更新 更多