Продвижение ссылок для производного класса без элементов данных

#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 , но это все. Оба могут использоваться как a Base , но ClassA и ClassB могут иметь совершенно разные сигнатуры в памяти, поэтому поиск ClassB конкретных вещей в a ClassA обречен с самого начала. Некоторые ограничения со временем ослабевают, и вы можете воспользоваться сходством между классами стандартной компоновки , и эти классы достаточно просты, чтобы соответствовать требованиям, но это в основном потому, что они ничего не содержат.

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 единственным другом.