C ++具有共享vptr的多态对象的容器

时间:2013-04-07 14:57:12

标签: c++ containers vtable polymorphism

假设我需要存储相同类型的对象集合,但是在编译时无法定义此类型。假设一旦定义了这种类型,它就永远不会改变。众所周知,当在编译时不知道类型时,可以使用指向其基类的指针容器来存储这些对象,即

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

这样,分配的对象不一定会并存在内存中,因为在每次分配时,new()将返回内存中的任意位置。此外,每个对象都嵌入了一个额外的指针(vptr),因为Base类当然需要是多态的。

对于这种特殊情况(类型定义一次+类型从不改变),上述解决方案不是最佳解决方案,因为从理论上讲,

  1. 没有必要为每个对象存储相同的vptr(sizeof()=指针大小):所有对象都指向相同的vtable;
  2. 可以使用连续的存储位置,因为对象的大小是在程序开头定义的,永远不会改变。
  3. 问:你们是否知道策略/容器/内存分配器/成语/技巧/其他任何可以解决这些问题的方法?

    我想我可以做这样的事情(使用经典的 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?
    };
    

    当然,这种方法在类型安全,对齐等方面存在很多问题。有什么建议吗?

    谢谢

3 个答案:

答案 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慢了。