使用纯虚拟类复制和交换习语

时间:2014-05-06 14:07:15

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

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

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

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&)|

4 个答案:

答案 0 :(得分:3)

当您的编译器通知您时,您无法创建抽象类型的变量。没有办法在那里跳舞。

这为您提供了三个主要选项:

停止使用纯虚函数

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

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

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

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

为此类实现的最重要的部分是一个构造函数,它对基类进行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); }。动态内存是必要的原因是因为派生类型可能需要比基类多得多的内存。

答案 1 :(得分:1)

你只是面对这样一个事实,即继承在复制语义方面工作得很笨拙。

例如,想象一下你找到了一个通过编译阶段的技巧,这意味着什么(下面的例子使用了赋值,但问题与副本相同):

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

答案 2 :(得分:0)

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

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

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

void B::print(ostream & os )
{
  os << m_att;
}
顺便说一下:

  • B从A私下继承,这是你想要的吗?
  • A副本构造函数中的初始化顺序错误
  • 您在A&#39的构造函数体中初始化了m_type,而不是在初始化列表中

答案 3 :(得分:0)

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

欢呼声, FM。