假设我需要存储相同类型的对象集合,但是在编译时无法定义此类型。假设一旦定义了这种类型,它就永远不会改变。众所周知,当在编译时不知道类型时,可以使用指向其基类的指针容器来存储这些对象,即
std::vector<Base*> collection;
collection.push_back( new Derived() );
这样,分配的对象不一定会并存在内存中,因为在每次分配时,new()
将返回内存中的任意位置。此外,每个对象都嵌入了一个额外的指针(vptr
),因为Base
类当然需要是多态的。
对于这种特殊情况(类型定义一次+类型从不改变),上述解决方案不是最佳解决方案,因为从理论上讲,
vptr
(sizeof()=指针大小):所有对象都指向相同的vtable
; 问:你们是否知道策略/容器/内存分配器/成语/技巧/其他任何可以解决这些问题的方法?
我想我可以做这样的事情(使用经典的 Shape 示例):
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?
};
当然,这种方法在类型安全,对齐等方面存在很多问题。有什么建议吗?
谢谢
答案 0 :(得分:3)
没有必要存储相同的vtable(每个对象8个字节?)collection.size()次;
你根本不存储vtables。无论对象是否具有多态性,您都存储大小相同的指针。如果集合具有N
个对象,则它需要N * sizeof(void*)
个字节。所以上述陈述是错误的。
可以使用连续的存储位置,因为它们的大小是已知的,并且在定义类型时彼此相等。
目前尚不清楚。如果你在谈论容器维护的存储,那么是的,由std::vector
维护的存储保证是连续的。
答案 1 :(得分: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() );
};
在这种情况下,您将保证连续存储不是多态对象的指针或引用,而是对象本身。
答案 2 :(得分:1)
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;
};
这样,您只需在需要时创建多态实例。对于存储,我们可以采用Massimiliano的解决方案。
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慢了。