15

注意:我知道以前在 SO 上已经提出过类似的问题,但我没有发现它们有帮助或很清楚。

第二个注意事项:对于这个项目/任务的范围,我试图避免使用第三方库,例如 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>需要是同质类型的。如果您需要将不同类型的对象放入一个向量中,您需要以某种方式擦除它们的类型并使它们看起来都相似。boost::any您可以使用or的道德等价物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 时,让此类抛出异常可能也很好。

这是我的一个项目中的 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 回答