2

同じ型のオブジェクトのコレクションを格納する必要があるとしますが、この型はコンパイル時に定義できません。また、このタイプが定義されると、変更されないとします。よく知られているように、コンパイル時に型がわからない場合、これらのオブジェクトは、基本クラスへのポインターのコンテナーを使用して格納できます。つまり、

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

このように、割り当てられたオブジェクトは必ずしもメモリ内に並べて格納されるとは限りません。これは、割り当てごとに がnew()メモリ内の任意の位置を返すためです。さらに、クラスはもちろんポリモーフィックである必要があるため、各オブジェクトに追加のポインター ( vptr) が埋め込まれています。Base

この特定のケース (型が一度定義され、型が変更されない) の場合、理論的には、上記のソリューションは最適なソリューションではありません。

  1. 各オブジェクトに同じ (sizeof() = ポインター サイズ) を格納する必要はありません。vptrそれらはすべて同じを指しvtableます。
  2. オブジェクトのサイズはプログラムの開始時に定義され、変更されることはないため、連続した格納場所を使用することができます。

Q:これらの問題を克服するための戦略/コンテナー/メモリ アロケーター/イディオム/トリック/その他の何かを知っていますか?

私はこのようなことができると思います (古典的な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?
};

もちろん、このアプローチには、型の安全性、配置などに関する多くの問題があります。何か提案はありますか?

ありがとうございました

4

3 に答える 3

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() );
};

この場合、ポリモーフィック オブジェクトへのポインターや参照ではなく、オブジェクト自体を連続して格納することが保証されます。

于 2013-04-07T15:16:41.240 に答える