Преобразование базового класса в производный класс

Я заинтересован, если это безопасно, чтобы удрученный (спасибо Майк) экземпляр базового класса для производного класса при определенных условиях. Я думаю, что образец является наиболее простым способом объяснить:

struct BaseA
{
void foo() const {}
double bar_member;
// no virtuals here
};

struct DerivedA : public BaseA
{
double bar(double z) {bar_member = z;return bar_member;}
// DerivedA does not add ANY member variables to BaseA.
// It also does not introduce ANY virtual functions.
};

struct BaseB
{
BaseA baseA;
};

// add extra functionality to B, to do this,
// i also need more functionality on baseA.
struct DerivedB : public BaseB
{
// is this "safe"? since BaseA and DerivedA
// should have the same memory layout?!?
DerivedA& getA() {return *static_cast<DerivedA*>(&baseA);}

double foo(double z) {return getA().bar(z);}
};

#include <iostream>

int main(int argc, char** argv)
{
DerivedB b;
// compiles and prints expected result
std::cout << b.foo(argc) << std::endl;
}

В моем случае классы BaseA и BaseB реализуют какую-то концепцию представления. Однако они также содержат все элементы данных, необходимые для добавления дополнительной функциональности в производные классы. Я знаю, что мог бы реализовать представление для хранения только ссылки на класс, обеспечивающий функциональность. Однако это имеет некоторые недостатки:

  • Мне нужно переписать весь интерфейс для классов представления.
  • В моем случае классы Derived обладают дополнительным аргументом шаблона (тип обратного вызова), который я хочу стереть в представлении. Следовательно, представление не должно содержать прямой ссылки на классы, обеспечивающие функциональность.

Я проверил свой код, он работает, но я не очень доверяю этому подходу. И да, я знаю, что мог бы достичь этого с помощью виртуалов и т. Д., Но это действительно производительность критична …

Любые идеи, советы, приветствуются

Мартин

для заинтересованных людей:
я изменил свой дизайн следующим образом:

struct DerivedB : public BaseB
{
// encapsule the required extended functionality of BaseA member
struct OperateOnBaseA
{
OperateOnBaseA(BaseA& a);
double dosomething(double);
};

OperateOnBaseA a_extension;

DerivedB() :a_extension(baseA) {}

double foo(double z) {return a_extension.dosomething();}
};

0

Решение

Что касается технической стороны: это, конечно, запрещено стандартом 2011 года, 5.2.9.11, статическое приведение. Пусть B будет основой D:

Если значение типа «указатель на cv1 B» указывает на B, который на самом деле является подобъектом объекта типа D, результирующий указатель указывает на включающий объект типа D. В противном случае результат приведения не определен.

С другой стороны, я был бы удивлен, если бы кто-то мог найти реализацию, которая не просто делает это, из-за очевидных реализаций классов, методов и статических приведений.

1

Другие решения

Ваш существующий код имеет неопределенное поведение, как указано в других ответах. Вы можете избежать этого, если не возражаете против действительно ужасного кода, уничтожив объект в baseA и создание DerivedA в том же месте, так что действителен downcast:

#include <new>

struct DerivedB : public BaseB
{
DerivedB()
{
static_assert( sizeof(BaseA) == sizeof(DerivedA), "same size" );
baseA.~BaseA();
::new(&baseA) DerivedA();
}
~DerivedB()
{
getA().~DerivedA();
::new(&baseA) BaseA();
}

DerivedA& getA() {return *static_cast<DerivedA*>(&baseA);}

double foo(double z) {return getA().bar(z);}
};

Деструктор восстанавливает объект исходного типа, так что когда BaseB деструктор уничтожает его baseA член он запускает деструктор правильного типа на объекте.

Но я бы избегал этого и перепроектировал ваши классы, чтобы решить это по-другому.

0

Я не считаю ваш подход достаточно чистым для того, что вы пытаетесь сделать. Предполагая, что есть «тип источника данных» SourceA и «тип представления данных» ViewBЯ бы пошел больше так:

#include <iostream>

using namespace std;

template<typename T>
class SourceA_base
{
protected:
T data;
public:
using value_type = T;
SourceA_base(T&& a) : data(std::move(a)) { }
SourceA_base(T const& a) : data() { }
void foo() const {}
};

template<typename T>
class SourceA : public SourceA_base<T>
{
using B = SourceA_base<T>;
public:
using B::B;
T bar(T z) { return B::data = z; }
};

template<typename U>
class ViewB_base
{
protected:
U&& source;
public:
using value_type = typename std::remove_reference<U>::type::value_type;
ViewB_base(U&& a) : source(std::forward<U>(a)) { }
};

template<typename U>
class ViewB : public ViewB_base<U>
{
using B = ViewB_base<U>;
using T = typename B::value_type;
public:
using B::B;
T foo(T z) { return B::source.bar(z); }
};

int main ()
{
using S = SourceA<double>;
S a{3.14};
ViewB<S&> b{a};
std::cout << b.foo(6.28) << std::endl; // compiles and prints expected result
std::cout << ViewB<S>{S{2}}.foo(4) << std::endl; // still works
}

Таким образом, все типы (источник / представление) являются шаблонными, представления содержат ссылки, и в них нет даункастов. При бронировании за использование ссылок:

  • Переписывание всего интерфейса: теперь нет необходимости, благодаря шаблонам.
  • Стирание типов обратного вызова. Во-первых, приложения, стирающие типы и критичные для производительности, не всегда являются хорошими друзьями. Во-вторых, лучше, чтобы обратный вызов удалял свой собственный базовый тип (а), а не представление стирало бы тип обратного вызова. Каждый класс должен делать свою работу. Или не стирайте типы и делайте их параметрами шаблона.

Я использовал rvalue-ссылки, так что все это работает и для временных, как показано во втором примере. Возможно, конструкторы не всегда завершены / правильны здесь. например за const Рекомендации; В реальности у меня были бы полностью шаблонные конструкторы (принимающие универсальные ссылки), но заставить их взаимодействовать с неявно определенными конструкторами копирования / перемещения с одним аргументом немного сложнее (нужны черты типа и enable_if) и я только хотел выделить идею здесь.

Вы также можете рассмотреть возможность использования кортежей для хранения данных, используя преимущества их пустой базовой оптимизации.

Что касается вашего первоначального вопроса, этот удрученный вариант я бы никогда не сделал; техническую сторону см. в ответе Питера Шнайдера.

0
По вопросам рекламы [email protected]