C ++ Контейнер полиморфных объектов с разделяемым vptr

Предположим, что мне нужно хранить коллекцию объектов того же типа, но этот тип не может быть определен во время компиляции. Предположим также, что после определения этого типа он никогда не изменится. Как хорошо известно, когда тип не известен во время компиляции, можно сохранить эти объекты, используя контейнер указателей на их базовый класс, т.е.

std::vector<Base*> collection;
collection.push_back( new Derived() );

Таким образом, выделенные объекты не обязательно будут храниться рядом в памяти, потому что при каждом выделении new() вернет произвольную позицию в памяти. Кроме того, есть дополнительный указатель (vptr) встроен в каждый объект, потому что Base класс конечно должен быть полиморфным.

Для этого конкретного случая (тип определяется один раз + тип никогда не меняется), приведенное выше решение не является оптимальным решением, поскольку теоретически

  1. не нужно хранить то же самое vptr (sizeof () = размер указателя) для каждого объекта: все они указывают на одно и то же vtable;
  2. Возможно использование смежных мест хранения, так как размер объектов определяется в начале программы и никогда не изменится.

Q: Ребята, вы знаете стратегию / контейнер / распределитель памяти / идиома / трюк / что-нибудь еще для преодоления этих проблем?

Я думаю, что я мог бы сделать что-то вроде этого (используя классический форма пример):

struct Triangle_Data {
double p1[3],p2[3],p3[3];
};

struct Circle_Data {
double radius;
};

struct Shape {
virtual double area() const = 0;
virtual char* getData() = 0;
virtual ~Shape() {}
};

struct Triangle : public Shape {
union {
Triangle_Data tri_data;
char data[sizeof(Triangle_Data)];
};
double area() const { /*...*/ };
char* getData() { return data; }
Triangle(char * dat_) {
std::copy(dat_, dat_+sizeof(Triangle_Data), this->data);
};
};

struct Circle : public Shape {
union {
Circle_Data circ_data;
char data[sizeof(Circle_Data)];
};
double area() const { /*...*/ };
char* getData() { return data; }
Circle(char * dat_) {
std::copy(dat_, dat_+sizeof(Circle_Data), this->data);
};
};

template<class BaseT>
struct Container {
int n_objects;
int sizeof_obj;
std::vector<char> data;
Container(...arguments here...) : ...init here... {
data.resize( sizeof_obj * n_objects );
}
void push_back(Shape* obj) {
// copy the content of obj
for( int i=0; i<sizeof_obj; ++i)
data.push_back(*(obj.getData() + i));
}
char* operator[] (int idx) {
return data + idx*sizeof_obj;
}
};

// usage:
int main() {
Container<Shape> collection( ..init here.. );
collection.push_back(new Circle());
cout << Circle(collection[0]).area() << endl; // horrible, but does it work?
};

Конечно, у этого подхода много проблем с безопасностью типов, выравниванием и т. Д. Есть предложения?

Спасибо

2

Решение

1) нет необходимости хранить одно и то же значение vptr (8 байт на объект?) Collection.size ();

Это не обязательно, но объекты не зависят друг от друга.

2) возможно использование смежных мест хранения, поскольку их размеры известны и равны друг другу при определении их типа.

… действительно, если вы можете хранить конкретные экземпляры, вы можете хранить их в непрерывной памяти.


Так что ты можешь сделать ?

Одним из решений является не используйте полиморфные экземпляры, но вместо этого разделяйте данные и полиморфизм:

struct IShape {
virtual ~IShape() {}
virtual double area() const = 0;
};

struct Circle {
float center, radius;
};

struct IShapeCircle: IShape {
IShapeCircle(Circle const& c): circle(const_cast<Circle&>(c)) {}
virtual double area() const { return PI * circle.radius * circle.radius; }

Circle& circle;
};

Таким образом, вы создаете полиморфный экземпляр только тогда, когда вам это нужно. А для хранения мы можем адаптировать решение Массимилиано.

struct IShapeVector {
virtual ~IShapeVector() {}
std::unique_ptr<IShape> get(size_t i) = 0;
std::unique_ptr<IShape const> get(size_t i) const = 0;
};

struct IShapeCircleVector: IShapeVector {
std::unique_ptr<IShape> get(size_t i) {
return make_unique<IShapeCircle>(_circles.at(i));
}
std::unique_ptr<IShape const> get(size_t i) const {
return make_unique<IShapeCircle const>(_circles.at(i));
}

std::vector<Circle> _circles;
};

Однако вы можете обнаружить, что трафик распределения / освобождения замедляет вас больше, чем просто v-ptr.

1

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

нет необходимости хранить одну и ту же vtable (8 байт на объект?) collection.size () раз;

Вы вообще не храните vtables. Вы храните указатели одинакового размера независимо от того, являются ли объекты полиморфными или нет. Если коллекция имеет N объекты, то требуется N * sizeof(void*) байт. Таким образом, приведенное выше утверждение является ложным.

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

Это не ясно. Если вы говорите о хранилище, поддерживаемом контейнером, то да, хранилище поддерживается std::vector гарантированно будет смежным.

3

Чтобы ответить на пункт 2 вашего вопроса, если вы знаете, что все объекты будут того же типа, и у вас есть эта информация во время выполнения, вы можете подумать о виртуализации контейнера и создать его на фабрике. Просто чтобы дать набросок идеи:

class ShapeContainer {
/* Virtual base */
};

class CircleContainer : public ShapeContainer {
/* ... */
private:
std::vector<Circle> impl_;
}

class ShapeContainerFactory {
/* Factory for ShapeContainer derived objects */
};

int main() {
ShapeContainer& collection = ShapeContainerFactory.create("Circle");
collection.push_back( Circle() );
};

В этом случае вам гарантировано будет хранить не только указатели или ссылки на полиморфные объекты, но и сами объекты.

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