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