C++: создание производного объекта через интерфейс базового класса
У меня есть шаблонный класс, который создается с использованием двух аргументов, целого числа и предыдущего экземпляра этого класса. Я хочу иметь возможность хранить экземпляры этих классов в контейнерах, поэтому я наследую их от базового класса (пожалуйста, не обращайте внимания на неумные указатели):
class base {
virtual base* getNext(unsigned x) = 0;
};
template <class D>
class derived :
public base {
/* no memory allocation here, simply changes the data in next */
void construct_impl(unsigned x, const derived<D>& previous, derived<D>& next);
derived(); /* default constructor */
derived(unsigned x, const derived<D>& previous) { /* construct from previous object */
allocate_memory_for_this();
construct_impl(x, previous, *this);
}
base* getNext(unsigned x) {
return new derived(x, *this);
}
};
Теперь я хотел бы создать функцию в base
класс, который будет строить объект derived<D>
так же, как construct_impl
делает, т.е. без выделения памяти заново. Я думал что-то вроде этого
class base {
virtual base* getNext(unsigned x) = 0;
virtual void getNext_noalloc(unsigned x, base* already_allocated_derived_object) = 0;
}
который будет переопределен в производном классе, как это
void getNext_noalloc(unsigned x, base* already_allocated_derived_object) {
construct_impl(x, *this, *already_allocated_derived_object);
}
который, к сожалению, не компилируется, так как нет преобразования из base*
в derived<D>*
(если я не использую static_cast). Есть ли способ добиться того, что мне нужно? Заранее спасибо!
2 ответа
Любопытно повторяющийся шаблон, с которым Дэвид Неем связался в комментариях, может сделать то, что вы ищете. Это не должно мешать вам хранить объекты производного класса вместе в одном контейнере. Похоже, вы реализуете двусвязный список с автоматическим созданием следующего элемента из заданного. (Это лишит законной силы список от этого элемента до конца, если это не хвост.)
Я считаю (я еще не пробовал), вы должны проверить dynamic_cast<>
в переопределениях для getNext_noalloc()
чтобы проверить next
указатель и вызов соответствующего класса construct_impl()
,
// override in derived class
void getNext_noalloc(unsigned x, base* already_allocated_derived_object) {
derived<D1>* p1 = dynamic_cast< derived<D1> >(already_allocated_derived_object);
derived<D2>* p2 = dynamic_cast< derived<D2> >(already_allocated_derived_object);
if(p1 != NULL) {
p1->construct_impl(x, *this, *p1); // 2nd parameter should take base type
} else if(p2 != NULL) {
p2->construct_impl(x, *this, *p2); // 2nd parameter should take base type
}
}
Предполагается, что эти два класса знают друг о друге, поэтому вы должны иметь определения функций после того, как классы были объявлены, и если construct_impl()
частные или защищенные, классы должны быть friend
s.
С помощью dynamic_cast<>()
Это должно означать, что вам не нужен CRTP, но вам придется проверять каждое приведение, чтобы убедиться, что оно преобразуется в правильный тип.
Возможно, вы работаете из-за неправильного понимания того, что на C++ можно написать
class ClownCar {
unsigned int x;
ClownCar inner_car;
};
Но это невозможно! Что бы sizeof(ClownCar)
быть? Это должно быть по крайней мере sizeof x + sizeof inner_car
; т.е. sizeof(unsigned int) + sizeof(ClownCar)
; то есть, по крайней мере, на четыре байта больше, чем он сам.
Таким образом, класс не может содержать экземпляр своего собственного класса. Наследование, виртуальное или иное, здесь не имеет значения. Так что же нам делать? Мы используем указатели!
class ClownCar {
unsigned int x;
ClownCar *inner_car;
public:
ClownCar() : x(0), inner_car(nullptr) {}
ClownCar(unsigned int x, ClownCar *previous) : x(x), inner_car(previous) {}
ClownCar *getNext(unsigned int x) {
return new ClownCar(x, this);
}
};
int main() {
ClownCar inmost_car;
ClownCar *car1 = inmost_car.getNext(42);
ClownCar *car2 = car1.getNext(43);
// ...
delete car2;
delete car1;
// of course we don't delete inmost_car, since it lives on the stack
}
Конечно, это не очень C++. Мы, вероятно, хотим избавиться от всех этих *
s, а также сделайте так, чтобы каждый автомобиль "вступал во владение" своим внутренним автомобилем (и также брал на себя ответственность за его удаление). Мы можем сделать это с помощью стандартной библиотеки std::unique_ptr
для представления этого понятия "владения" (см. также Как передать аргумент unique_ptr в конструктор или функцию?)... но на самом деле все, что у нас есть, это односвязный список ClownCar
s, и это то, что STL дает нам бесплатно:
struct ClownCar { unsigned int x; };
typedef std::list<ClownCar> ClownCarList; // ta-da!
Поэтому я думаю, что реальный вопрос в том, чего вы пытаетесь достичь?