В настоящее время я изучаю взаимодействие между полиморфными типами и операциями присваивания. Моя основная проблема заключается в том, может ли кто-то попытаться присвоить значение базового класса объекту производного класса, что вызовет проблемы.Определить назначение базового класса на ссылку, указывающую на производный класс
От this answer Я узнал, что оператор присваивания базового класса всегда скрыт неявно определенным оператором присваивания производного класса. Поэтому для назначения простой переменной неправильные типы вызовут ошибки компилятора. Однако, это не так, если назначение происходит через ссылку:
class A { public: int a; };
class B : public A { public: int b; };
int main() {
A a; a.a = 1;
B b; b.a = 2; b.b = 3;
// b = a; // good: won't compile
A& c = b;
c = a; // bad: inconcistent assignment
return b.a*10 + b.b; // returns 13
}
Эта форма уступки, скорее всего, приведет к inconcistent состояния объекта, однако нет никакого предупреждения компилятора и код выглядит не зол мне в первом взгляд.
Есть ли установленная идиома для обнаружения таких проблем?
Я думаю, я могу только надеяться на обнаружение во время выполнения, бросая исключение, если найду такое недопустимое назначение. Лучший подход, который я могу сейчас представить, - это определяемый пользователем оператор присваивания в базовом классе, который использует информацию типа времени выполнения, чтобы гарантировать, что this
на самом деле является указателем на экземпляр базы, а не на производный класс, а затем выполняет ручную поэтапную копию. Это звучит как много накладных расходов и сильно влияет на читаемость кода. Есть что-то проще?
Редактировать: Поскольку применимость некоторых подходов, похоже, зависит от того, что я хочу сделать, вот некоторые подробности.
У меня есть две математические концепции, скажем ring и field. Каждое поле является кольцом, но не наоборот. Для каждого из них имеется несколько реализаций, и они имеют общие базовые классы, а именно AbstractRing
и AbstractField
, причем последний получен из первого. Теперь я пытаюсь реализовать легкую для записи семантику по-ссылке, основанную на std::shared_ptr
. Таким образом, мой класс Ring
содержит std::shared_ptr<AbstractRing>
с его реализацией и кучу методов, пересылающих на него. Я хотел бы написать Field
как наследующий от Ring
, поэтому мне не нужно повторять эти методы. Методы, специфичные для поля, просто набрасывают указатель на AbstractField
, и я бы хотел сделать это статически. Я могу убедиться, что указатель на самом деле AbstractField
при строительстве, но я беспокоюсь, что кто-то назначит Ring
Ring&
, который на самом деле является Field
, тем самым нарушив мой предполагаемый инвариант относительно содержащегося общего указателя.
Не является ли здесь настоящей проблемой, что у вас есть не абстрактный базовый класс? –
Лично я отключу конструктор и операторы присваивания для полиморфных типов. Наследственный базовый полиморфизм действительно не очень хорош в отношении типов значений. –
@OliCharlesworth: Я не вижу, как. Подумайте, например. кнопка переключения, полученная с помощью кнопки, я вижу ситуации, когда базовый класс должен быть реалистичным, и проблема может возникнуть в реальном мире. Поэтому я не следовал бы за всеми принципами «все основания должны быть абстрактными». Если это не то, что вы имели в виду, то, пожалуйста, уточните, как абстрактные базовые классы могут помочь в моей ситуации. – MvG