15

注:これと同様の質問が以前にSOで尋ねられたことは知っていますが、役に立たなかったり、非常に明確であるとは思いませんでした。

2 番目の注意:このプロジェクト/割り当ての範囲については、Boost などのサード パーティのライブラリを避けようとしています。

それぞれのインデックスで、単一のベクトルに複数の型を保持させる方法があるかどうかを確認しようとしています。たとえば、次のコード サンプルがあるとします。

vector<something magical to hold various types> vec;
int x = 3;
string hi = "Hello World";
MyStruct s = {3, "Hi", 4.01};

vec.push_back(x);
vec.push_back(hi);
vec.push_back(s);

うまくいくと聞いたvector<void*>ことがありますが、メモリの割り当てが難しくなり、特定のインデックスに挿入された値が予想よりも大きい場合、近くのメモリの特定の部分が意図せず上書きされる可能性が常にあります。

私の実際のアプリケーションでは、ベクトルに挿入される可能性のある型はわかっていますが、これらの型がすべて同じスーパークラスから派生しているわけではなく、これらすべての型がベクトルまたはどのような順序でプッシュされるかという保証はありません。 .

コード サンプルで示した目的を安全に達成できる方法はありますか?

お時間をいただきありがとうございます。

4

3 に答える 3

22

オブジェクトstd::vector<T>は同種の型である必要があります。異なるタイプのオブジェクトを 1 つのベクトルに配置する必要がある場合は、何らかの方法でそれらのタイプを消去し、それらをすべて同じように見せる必要があります。boost::anyまたはの道徳的な等価物を使用できますboost::variant<...>。の考え方はboost::any、型階層をカプセル化し、基本へのポインターを格納しますが、テンプレート化された派生を指すことです。非常にラフで不完全なアウトラインは次のようになります。

#include <algorithm>
#include <iostream>

class any
{
private:
    struct base {
        virtual ~base() {}
        virtual base* clone() const = 0;
    };
    template <typename T>
    struct data: base {
        data(T const& value): value_(value) {}
        base* clone() const { return new data<T>(*this); }
        T value_;
    };
    base* ptr_;
public:
    template <typename T> any(T const& value): ptr_(new data<T>(value)) {}
    any(any const& other): ptr_(other.ptr_->clone()) {}
    any& operator= (any const& other) {
        any(other).swap(*this);
        return *this;
    }
    ~any() { delete this->ptr_; }
    void swap(any& other) { std::swap(this->ptr_, other.ptr_); }

    template <typename T>
    T& get() {
        return dynamic_cast<data<T>&>(*this->ptr_).value_;
    }
};

int main()
{
    any a0(17);
    any a1(3.14);
    try { a0.get<double>(); } catch (...) {}
    a0 = a1;
    std::cout << a0.get<double>() << "\n";
}
于 2012-11-19T20:57:10.160 に答える
9

示唆されているように、さまざまな形式の共用体、バリアントなどを使用できます。基本クラス インターフェイスで必要なすべての操作を定義できれば、格納されたオブジェクトで何をしたいかによって、外部ポリモーフィズムはまさにあなたが望むことを行うことができます。

オブジェクトをコンソールに出力するだけの場合の例を次に示します。

#include <iostream>
#include <string>
#include <vector>
#include <memory>

class any_type
{
public:
   virtual ~any_type() {}
   virtual void print() = 0;
};

template <class T>
class concrete_type : public any_type
{
public:
   concrete_type(const T& value) : value_(value)
   {}

   virtual void print()
   {
      std::cout << value_ << '\n';
   }
private:
   T value_;
};

int main()
{
   std::vector<std::unique_ptr<any_type>> v(2);

   v[0].reset(new concrete_type<int>(99));
   v[1].reset(new concrete_type<std::string>("Bottles of Beer"));

   for(size_t x = 0; x < 2; ++x)
   {
      v[x]->print();
   }

   return 0;
}
于 2012-11-19T21:30:05.550 に答える
8

そのためには、ベクトルからオブジェクトの型情報を何らかの形で隠すためのラッパー クラスが必ず必要になります。

また、以前に Type-B を格納したときに Type-A を取得しようとしたときに、このクラスが例外をスローするようにすることもおそらく良いことです。

これは、私のプロジェクトの 1 つからの Holder クラスの一部です。おそらくここから始めることができます。

注: 無制限の共用体を使用しているため、これは C++11 でのみ機能します。これに関する詳細は、C++11 で提案されている無制限の共用体とは何ですか? を参照してください。

class Holder {
public:
    enum Type {
        BOOL,
        INT,
        STRING,
        // Other types you want to store into vector.
    };

    template<typename T>
    Holder (Type type, T val);

    ~Holder () {
        // You want to properly destroy
        // union members below that have non-trivial constructors
    }

    operator bool () const {
        if (type_ != BOOL) {
           throw SomeException();
        }
        return impl_.bool_;
    }
    // Do the same for other operators
    // Or maybe use templates?

private:
    union Impl {
        bool   bool_;
        int    int_;
        string string_;

        Impl() { new(&string_) string; }
    } impl_;

    Type type_;

    // Other stuff.
};
于 2012-11-19T20:58:12.690 に答える