7

我正在尝试使用纯虚拟方法和“复制和交换”习语来实现虚拟类,但遇到了一些问题。代码无法编译,因为我在包含纯虚方法的类 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&)|
4

4 回答 4

4

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

这给您留下了三个主要选择:

停止使用纯虚函数

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

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

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

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

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

这会导致与第一个选项相同的切片问题。根据您正在做的事情,这可能是您的预期结果。

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

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

使用复制工厂

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

于 2014-05-06T14:22:50.133 回答
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;
于 2014-05-06T14:38:09.660 回答
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&);
};

欢呼,调频。

于 2020-11-12T16:53:06.613 回答
-1

编译器是对的。该类A是抽象类,因此您不能在operator=.

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

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

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

顺便一提 :

  • B 从 A 私下继承,这是你想要的吗?
  • A 的复制构造函数中的初始化顺序错误
  • 您在 A 的构造函数主体中初始化 m_type 而不是在初始化列表中
于 2014-05-06T14:28:48.850 回答