#c #reference #derived-class
Вопрос:
У меня есть интересная проблема, связанная с иерархией классов в библиотеке, которую я поддерживаю. Очень упрощенный взгляд на ситуацию выглядит следующим образом:
class Base {
// private data public interface to said data
};
class ClassA : public Base {
// Behaviour
};
class ClassB : public Base {
// Behaviour
};
Итак, здесь у меня есть класс, который содержит данные в частном порядке и имеет согласованный интерфейс. На самом деле это шаблонный класс с множеством различных моделей хранения. Два производных класса ClassA
и ClassB
просто добавляют разные реализации одного и того же поведения и не содержат никаких данных. Это должно быть в пределах возможностей преобразования ссылки на экземпляр ClassA
в один из ClassB
без вызова каких-либо копий. Конечно, можно использовать
ClassA a;
Bamp; a_bref = *reintepret_cast<B*>(amp;a);
Но это нарушает все правила. Мой вопрос: Существует ли безопасный способ реализации такого оператора преобразования?
Комментарии:
1. объявите и реализуйте оператор приведения
2. Подожди секунду… Без копий? Нет. А
ClassA
-это не аClassB
. В них есть что-то общееBase
, но это все. Оба могут использоваться как aBase
, ноClassA
иClassB
могут иметь совершенно разные сигнатуры в памяти, поэтому поискClassB
конкретных вещей в aClassA
обречен с самого начала. Некоторые ограничения со временем ослабевают, и вы можете воспользоваться сходством между классами стандартной компоновки , и эти классы достаточно просты, чтобы соответствовать требованиям, но это в основном потому, что они ничего не содержат.3. К сожалению, я думаю, что правильный способ думать об этой структуре не с точки зрения иерархии классов, а вместо этого как о системе признаков, реализованной для класса хранения данных (например, модель Rust).
4. Вы находитесь в одном из тех случаев, когда «Вероятно, сработает», потому что вы не меняете данные в подклассах, но все, что требуется, — это один программист, который появится позже, чтобы полностью все испортить. Возможно, вам захочется взглянуть на шаблон посетителей . Мне кажется, что это достойное направление для того, что я понимаю в вашем случае использования.
5. на самом деле, если класс имеет виртуальные функции, reinterpret_cast вам не поможет, объект все равно будет иметь ссылку на vtable фактического класса.
Ответ №1:
Множественное наследование
Один из способов достижения вашей цели-это множественное наследование.
class A : virtual public Base {
//...
};
class B : virtual public Base {
//...
};
class AorB : public A, public B {
//...
};
При виртуальном наследовании у вас есть только один Base
экземпляр, который совместно используется между A
составляющими и B
составляющими AorB
. Этот подход означает, что вам нужно создать класс, который знает, какие подтипы вы, возможно, захотите просмотреть. Это может быть или не быть проблемой для вас, в зависимости от вашего варианта использования.
СТРАТЕГИИ
Более гибкий подход может заключаться в том, чтобы рассматривать A
и B
как стратегии, и рассматривать Base
как контекст. В этом методе вы не будете использовать наследование. Вы можете отделить данные от интерфейса, чтобы A
и B
могли наследовать методы доступа, но ссылаться на данные.
class Base {
friend class Base_Interface;
//...
};
class Base_Interface {
Base amp;context_;
//...
Base_Interface (Base amp;context) : context_(context) {}
template <typename X> operator X () { return context_; }
};
class A : public Base_Interface {
//...
A (Base amp;context) : Base_Interface(context) {}
};
class B : public Base_Interface {
//...
B (Base amp;context) : Base_Interface(context) {}
};
Возможно, лениво, существует метод преобразования шаблона, позволяющий пользователям Base_Interface
конвертировать в какой-либо другой класс, который принимает Base
его в своем конструкторе. Он работает, если Base
у него нет публичных участников, с Base_Interface
единственным другом.