Я пытаюсь реализовать виртуальный класс с чисто виртуальным методом и идиомой «копировать и менять», но я столкнулся с некоторыми проблемами. Код не скомпилируется, потому что я создаю экземпляр в операторе присвоения класса 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&)|
Как сообщает ваш компилятор, вы не можете создать переменную абстрактного типа. Там нет никакого способа танцевать вокруг этого.
Это оставляет вам три основных варианта:
Во-первых, вы можете просто избавиться от чисто виртуальных методов и предоставить небольшую заглушку в каждом из них, которая вызывает 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); }
, Причина, по которой динамическая память необходима, заключается в том, что вашим производным типам может потребоваться произвольно больше памяти, чем вашему базовому классу.
Вы просто сталкиваетесь с тем фактом, что наследование неудобно работает с семантикой копирования.
Например, представьте, что вы нашли способ пройти этап компиляции, что будет означать (в следующем примере используется присваивание, но проблема с копией та же):
// 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;
Компилятор прав. Класс A
является абстрактным классом, поэтому вы не можете создавать его экземпляры в operator=
,
В Б, вы только что объявили print
функция, но вы не реализовали это. Это означает, что вы получите ошибки связывания.
Реализуя его, он прекрасно компилируется (если мы игнорируем различные предупреждения):
void B::print(ostream & os )
{
os << m_att;
}
Кстати :