Последний шаг, чтобы связать все это, RTTI:
Вы можете использовать RTTI для правильной обработки виртуальных функций, которые принимают ваш тип. Вот последняя часть головоломки, чтобы понять, как правильно обрабатывать присваивания при работе с возможно унаследованными типами.
virtual B& operator=(const B& right)
{
const D *pD = dynamic_cast<const D*>(&right);
if(pD)
{
x = pD->x;
y = pD->y;
}
else
{
x = right.x;
y = 13;//default value
}
return *this;
}
Я хотел бы добавить к этому решению несколько замечаний. У оператора присваивания, объявленного так же, как указано выше, есть три проблемы.
Компилятор генерирует оператор присваивания, который принимает аргумент const D & , который не является виртуальным и не выполняет то, что, как вы думаете, делает.
Вторая проблема - тип возврата, вы возвращаете базовую ссылку на производный экземпляр. Вероятно, не большая проблема, так как код работает в любом случае. Тем не менее, лучше возвращать ссылки соответственно.
Третья проблема, оператор присваивания производного типа не вызывает оператор присваивания базового класса (что, если есть частные поля, которые вы хотели бы скопировать?), Объявление оператора присваивания как виртуального не заставит компилятор сгенерировать его для вас. Это скорее побочный эффект отсутствия двух или более перегрузок оператора присваивания для получения желаемого результата.
Учитывая базовый класс (такой же, как в приведенном мною посте):
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
Следующий код дополняет решение RTTI, которое я цитировал:
class D : public B{
public:
// The virtual keyword is optional here because this
// method has already been declared virtual in B class
/* virtual */ const D& operator =(const B& b){
// Copy fields for base class
B::operator =(b);
try{
const D& d = dynamic_cast<const D&>(b);
// Copy D fields
y = d.y;
}
catch (std::bad_cast){
// Set default values or do nothing
}
return *this;
}
// Overload the assignment operator
// It is required to have the virtual keyword because
// you are defining a new method. Even if other methods
// with the same name are declared virtual it doesn't
// make this one virtual.
virtual const D& operator =(const D& d){
// Copy fields from B
B::operator =(d);
// Copy D fields
y = d.y;
return *this;
}
int y;
};
Это может показаться полным решением, это не так. Это не полное решение, потому что при выводе из D вам потребуется 1 оператор =, который принимает const B & , 1 оператор =, который принимает const D & , и один оператор, который принимает const D2 & . Вывод очевиден, число перегрузок operator = () эквивалентно количеству суперклассов + 1.
Учитывая, что D2 наследует D, давайте посмотрим, как выглядят два унаследованных метода operator = ().
class D2 : public D{
/* virtual */ const D2& operator =(const B& b){
D::operator =(b); // Maybe it's a D instance referenced by a B reference.
try{
const D2& d2 = dynamic_cast<const D2&>(b);
// Copy D2 stuff
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
/* virtual */ const D2& operator =(const D& d){
D::operator =(d);
try{
const D2& d2 = dynamic_cast<const D2&>(d);
// Copy D2 stuff
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
};
Очевидно, что оператор = (const D2 &) просто копирует поля, представьте, как будто это было там. Мы можем заметить закономерность в унаследованных перегрузках operator = (). К сожалению, мы не можем определить методы виртуальных шаблонов, которые позаботятся об этом шаблоне, нам нужно несколько раз скопировать и вставить один и тот же код, чтобы получить полный полиморфный оператор присваивания, единственное решение, которое я вижу. Также относится к другим бинарным операторам.
Редактировать
Как уже упоминалось в комментариях, самое меньшее, что можно сделать, чтобы упростить жизнь, - это определить самый верхний оператор присваивания суперкласса = () и вызвать его из всех других методов оператора суперкласса = (). Также при копировании полей можно определить метод _copy.
class B{
public:
// _copy() not required for base class
virtual const B& operator =(const B& b){
x = b.x;
return *this;
}
int x;
};
// Copy method usage
class D1 : public B{
private:
void _copy(const D1& d1){
y = d1.y;
}
public:
/* virtual */ const D1& operator =(const B& b){
B::operator =(b);
try{
_copy(dynamic_cast<const D1&>(b));
}
catch (std::bad_cast){
// Set defaults or do nothing.
}
return *this;
}
virtual const D1& operator =(const D1& d1){
B::operator =(d1);
_copy(d1);
return *this;
}
int y;
};
class D2 : public D1{
private:
void _copy(const D2& d2){
z = d2.z;
}
public:
// Top-most superclass operator = definition
/* virtual */ const D2& operator =(const B& b){
D1::operator =(b);
try{
_copy(dynamic_cast<const D2&>(b));
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
// Same body for other superclass arguments
/* virtual */ const D2& operator =(const D1& d1){
// Conversion to superclass reference
// should not throw exception.
// Call base operator() overload.
return D2::operator =(dynamic_cast<const B&>(d1));
}
// The current class operator =()
virtual const D2& operator =(const D2& d2){
D1::operator =(d2);
_copy(d2);
return *this;
}
int z;
};
Нет необходимости в методе set defaults , потому что он получит только один вызов (в перегруженном базовом операторе = ()). Изменения, когда копирование полей выполняется в одном месте и все перегрузки operator = () затрагиваются и выполняют свое предназначение.
Спасибо sehe за предложение.