【问题标题】:C++ Push Multiple Types onto VectorC++ 将多种类型推送到向量上
【发布时间】:2012-11-07 20:25:40
【问题描述】:

注意:我知道之前有人在 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&lt;void*&gt; 可以工作,但是内存分配会变得很棘手,如果插入某个索引的值大于预期,附近内存中的某些部分总是可能会被无意覆盖.

在我的实际应用中,我知道可以将哪些可能类型插入到向量中,但是这些类型并不都派生自同一个超类,也不能保证所有这些类型将被推到向量上或以什么顺序。

有没有一种方法可以安全地实现我在代码示例中展示的目标?

感谢您的宝贵时间。

【问题讨论】:

  • @chris 抱歉,我不清楚。对于这个项目/任务的范围,我试图避免使用第三方库。我已经更新了我的问题。
  • 好吧,我不知道它的效果如何,但你可以拥有一个包含type 成员和所有可能类型联合的结构。
  • 如果您强制要求您无法知道实际推送的是什么,我们可以假设这个向量除了指针之外不拥有任何东西吗?如果没有在所有者级别建立类型信息的某种方式,您在正确管理清理或擦除方面会有些麻烦。带有指针+析构函数引用的结构体是可以想象的,但到时候你就得开始问自己这里服务的真实数据模型是什么?
  • 你真的想要完全随机的数据,还是这些有某种关系?如果存在某种类型层次结构,您可以存储指向它们共享的更通用类型的指针,并且可以使用 shared_ptr 来减少内存混乱。
  • @BrendanLong 假设这个向量将从成员函数返回,那么像你所说的指针真的可能吗?

标签: c++ memory-management types vector


【解决方案1】:

std::vector&lt;T&gt; 持有的对象必须是同质类型。如果您需要将不同类型的对象放入一个向量中,您需要以某种方式擦除它们的类型并使它们看起来都相似。您可以使用boost::anyboost::variant&lt;...&gt; 的道德等价物。 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";
}

【讨论】:

  • +1,很好的答案。虽然在数据中记住,clone() 应该是data&lt;T&gt;* clone() const,因为它是协变的 :) 但这可以说是比公认的答案更好的答案。
  • 必须知道获取数据的类型使得该解决方案对许多应用程序没有帮助 - 即使用 a0.get()。如果我们假设我们知道类型,为什么不直接使用 void 指针?
  • @MatthaeusGaiusCaesar 差异可能很小,但您可以检查其中实际存在什么类型,例如,使用dynamic_cast&gt;。如果您愿意,您可以做得更好,例如,通过将类型检查分派到产生std::type_info const&amp;virtual 函数中。 Boost anystd::any(与 C++17 一起添加)有效地做到了这一点,并且添加到简短的演示实现中并不难。该代码只是一个基本草案,答案是这样的。为了让它变得更好,你需要添加一些东西——或者使用std::any,它在写答案时并不存在。
【解决方案2】:

正如建议的那样,您可以使用各种形式的联合、变体等。根据您想要对存储对象执行的操作,外部多态可以完全按照您的意愿执行,如果您可以定义所有必要的基类接口中的操作

如果我们只想将对象打印到控制台,这是一个示例:

#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;
}

【讨论】:

  • 有没有办法拥有,而不是print() 一个T get() 函数,你可以以某种方式到v[x]::T result = v[x].get()
  • 建议您将这部分描述加粗“如果您可以在基类接口中定义所有必要的操作”,这很重要,否则解决方案将不起作用。值得注意的是,同样适用于成员变量?
【解决方案3】:

为了做到这一点,您肯定需要一个包装类来以某种方式从向量中隐藏对象的类型信息。

当您在之前已将 Type-B 存储到其中的情况下尝试取回 Type-A 时,让此类抛出异常可能也很好。

这是我的一个项目中的 Holder 类的一部分。你大概可以从这里开始。

注意:由于使用了不受限制的联合,这仅适用于 C++11。 可以在此处找到更多信息:What are Unrestricted Unions proposed in 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.
};

【讨论】:

  • 我很确定你不能把 std::string 放在一个联合中,因为 9.5/1 说如果它有一个非平凡的构造函数、复制构造函数、析构函数,你就不能这样做, 或复制赋值运算符。
  • 您现在可以在 c++11 中使用,它具有所有优点:p 您可以在 wikipedia 参考它。
  • 您可能应该注意到,在您的回答中,这是一个仅限 C++11 的解决方案,并且注意到您必须使用放置新和显式销毁才能使用 string会员安全。
  • 修改了我的答案,声明它是 C++11-only :-)
  • 有人分析过这段代码吗?这个解决方案看起来很干净,但它比传统的多态性 + virtual 或 dynamic_cast 快还是慢?
猜你喜欢
  • 1970-01-01
  • 2017-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-19
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
相关资源
最近更新 更多