скопируйте и поменяйте идиомы с чистого виртуального класса

Я пытаюсь реализовать виртуальный класс с чисто виртуальным методом и идиомой «копировать и менять», но я столкнулся с некоторыми проблемами. Код не скомпилируется, потому что я создаю экземпляр в операторе присвоения класса 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&)|

5

Решение

Как сообщает ваш компилятор, вы не можете создать переменную абстрактного типа. Там нет никакого способа танцевать вокруг этого.

Это оставляет вам три основных варианта:

Прекратите использовать чисто виртуальные функции

Во-первых, вы можете просто избавиться от чисто виртуальных методов и предоставить небольшую заглушку в каждом из них, которая вызывает std::terminate, что, очевидно, нарушило бы определение времени компиляции того, все ли (бывшие) чисто виртуальные методы переопределены во всех производных классах.

Это приведет к нарезка, поскольку он будет копировать только базовый класс, и все, что делает производный класс, потеряно.

Используйте класс заглушки без чисто виртуальных функций

Аналогично этому, вы можете создать производный класс, который реализует все виртуальные методы с простыми заглушками (возможно, вызов std::terminate), и используется только как «инстанцируемая версия базового класса».

Наиболее важной частью для реализации этого класса был бы конструктор, который принимает константную ссылку на базовый класс, так что вы можете просто использовать его вместо копирования базового класса. Этот пример также добавляет конструктор перемещения, потому что я фетишист производительности.

Это вызывает то же самое нарезка проблема как первый вариант. Это может быть вашим предполагаемым результатом, основанным на том, что вы делаете.

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

Другие решения

Вы просто сталкиваетесь с тем фактом, что наследование неудобно работает с семантикой копирования.

Например, представьте, что вы нашли способ пройти этап компиляции, что будет означать (в следующем примере используется присваивание, но проблема с копией та же):

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

Компилятор прав. Класс A является абстрактным классом, поэтому вы не можете создавать его экземпляры в operator=,

В Б, вы только что объявили print функция, но вы не реализовали это. Это означает, что вы получите ошибки связывания.

Реализуя его, он прекрасно компилируется (если мы игнорируем различные предупреждения):

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

Кстати :

  • B наследует в частном порядке от A, это то, что вы хотели?
  • неправильный порядок инициализации в конструкторе копирования A
  • вы инициализировали m_type в теле конструктора А, а не в списке инициализации
0
По вопросам рекламы [email protected]