Я заинтересован, если это безопасно, чтобы удрученный (спасибо Майк) экземпляр базового класса для производного класса при определенных условиях. Я думаю, что образец является наиболее простым способом объяснить:
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 реализуют какую-то концепцию представления. Однако они также содержат все элементы данных, необходимые для добавления дополнительной функциональности в производные классы. Я знаю, что мог бы реализовать представление для хранения только ссылки на класс, обеспечивающий функциональность. Однако это имеет некоторые недостатки:
Я проверил свой код, он работает, но я не очень доверяю этому подходу. И да, я знаю, что мог бы достичь этого с помощью виртуалов и т. Д., Но это действительно производительность критична …
Любые идеи, советы, приветствуются
Мартин
для заинтересованных людей:
я изменил свой дизайн следующим образом:
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();}
};
Что касается технической стороны: это, конечно, запрещено стандартом 2011 года, 5.2.9.11, статическое приведение. Пусть B будет основой D:
Если значение типа «указатель на cv1 B» указывает на B, который на самом деле является подобъектом объекта типа D, результирующий указатель указывает на включающий объект типа D. В противном случае результат приведения не определен.
С другой стороны, я был бы удивлен, если бы кто-то мог найти реализацию, которая не просто делает это, из-за очевидных реализаций классов, методов и статических приведений.
Ваш существующий код имеет неопределенное поведение, как указано в других ответах. Вы можете избежать этого, если не возражаете против действительно ужасного кода, уничтожив объект в 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
член он запускает деструктор правильного типа на объекте.
Но я бы избегал этого и перепроектировал ваши классы, чтобы решить это по-другому.
Я не считаю ваш подход достаточно чистым для того, что вы пытаетесь сделать. Предполагая, что есть «тип источника данных» 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
) и я только хотел выделить идею здесь.
Вы также можете рассмотреть возможность использования кортежей для хранения данных, используя преимущества их пустой базовой оптимизации.
Что касается вашего первоначального вопроса, этот удрученный вариант я бы никогда не сделал; техническую сторону см. в ответе Питера Шнайдера.