【问题标题】:How can I store objects of differing types in a C++ container?如何在 C++ 容器中存储不同类型的对象?
【发布时间】:2011-01-19 17:14:55
【问题描述】:

是否有我可以使用或构建的 C++ 容器可以包含 intstringdouble 类型?我面临的问题是,每当我尝试使用以下内容填充地图、矢量或列表时:

int x;
string y;
double z;

我受到格式的限制:

list<int> mycountainer;
vector<string> mycontainer;

强制mycontainer 只包含一种类型。

在有人建议泛型之前,这也行不通,因为 C++ 附带的标准 vectorlist 容器已经是泛型的 - 它们可以是任何类型的容器,但不能包含多种类型。

如果可能的话,我也想避免使用 Boost - 如果有一种简单的方法可以让我自己编写代码,我会更喜欢它。

【问题讨论】:

  • 呃...struct有人吗?还是class
  • 你试过用union吗?
  • @Elalfer:在当前的标准中(这将随着 C++0x 改变)你不能在联合中使用std::string
  • 你为什么要这样做?你打算如何使用容器?您将如何验证您插入的内容?

标签: c++ types polymorphism containers


【解决方案1】:

您可以使用(或重新实现)boost::any 并将boost::any 的实例存储在容器中。这将是最安全的,因为boost::any 可能已经处理了在一般情况下解决此类问题所涉及的大部分边缘情况和复杂性。

如果您想做一些快速而肮脏的事情,请创建一个结构或联合,其中包含所有潜在类型的成员以及枚举或对象中哪种类型“活动”的其他指示符。请特别注意联合,因为它们具有一些有趣的属性(例如,如果您读取错误的联合成员,则会调用未定义的行为,一次只能有一个成员处于“活动”状态,即最近写入的那个)。

不过,我很好奇你在做什么,你需要这样的构造。

【讨论】:

  • +1 使用boost::any。此外,正如我在对@Elalfer 的评论中所述,您不能将std::string 存储在联合中。
  • 呃,是的,你说得对,是我的疏忽。当时我碰巧浏览了 0x 标准草案(这显然允许 std::strings 等通过它的“隐式删除函数”魔法在联合中)。
  • 如果您不想使用 Boost 并且无法访问 C++17,您可以使用 std::any :)
【解决方案2】:

嗯,第一个问题是:你为什么认为你需要在同一个容器中存储不同、完全不相关类型的对象?我觉得这很可疑.

如果我有需要,我会查看boost::variantboost::any

【讨论】:

  • 一点也不可疑。我经常编写程序,我想将覆盖存储在以字符串或枚举标识为键的映射中。我希望它们在一个单一的结构中,这样我就可以完全传递它们,我希望它们是任何类型,因为替代方法是将它们存储为字符串并每次都解析它们。但是,是的,boost::any 或 boost::variant 是要走的路。
  • 我认为一个更好的问题是,在我快速解雇某人之前,“boost::variant 的目的是什么”。它有什么区别?它是抽象的,它是实用的,它是你想要的任何东西。克服它。
  • @Dylan:boost::variantboost::any的目的是存储不相关的对象。因此,我使用过这样的类型,但我怀疑它每十年不止一次。因此,我建议质疑设计。
  • 如果容器代表游戏的“库存”,其中包含不同的“对象”,您可能必须在容器中存储不同的对象......
  • 我说的不是不同的类型,我说的是不相关的类型。
【解决方案3】:

您想要的称为“异质容器”。 C++ 在 STL 中技术上不支持它们,但 Boost 支持。

鉴于此,我想你会在这个问题中找到答案:how-do-you-make-a-heterogeneous-boostmap

【讨论】:

    【解决方案4】:

    您可以使用结构、类或 std::pair。

    [编辑]

    对于类和结构:

    struct XYZ {
        int x;
        string y;
        double z;
    };
    std::vector<XYZ> container;
    
    XYZ el;
    el.x = 10;
    el.y = "asd";
    el.z = 1.123;
    container.push_back(el);
    

    对于 std::pair:

    #include <pair>
    typedef std::pair<int, std::pair<string, double> > XYZ;
    std::vector<XYZ> container;
    container.push_back(std::make_pair(10, std::make_pair("asd", 1111.222)));
    

    【讨论】:

    • 为什么一对事物和一对而不是元组?
    • 当时标准 C++ 中没有元组。
    • 那些不是不同类型的元素。这些都是相同类型的元素,每个元素都包含不同但常量类型的成员。
    【解决方案5】:

    您可以使用包含所有三个的结构。

    struct Data
    {
        int intVal;
        std::string stringVal;
        double doubleVal;
    };
    

    然后您可以声明list mycontainer&lt;Data&gt; 并使用适当的值,前提是您知道值类型是什么。如果没有,请在结构中添加一个附加字段,告诉您正在使用三种数据类型中的哪一种。

    struct Data
    {
        enum DATATYPE { DT_INT, DT_STRING, DT_DOUBLE } type;
    
        int intVal;
        std::string stringVal;
        double doubleVal;
    };
    

    如果您担心内存使用情况,您可能会使用联合,但我倾向于避免使用它们。不过,这可能是我不必要的偏执狂。

    【讨论】:

    • 联合仅限于简单的数据类型,而 std::string 则不是。但是有 boost::variant; boost.org/doc/libs/1_45_0/doc/html/variant.html
    • 联合不限于简单的数据类型 - 您可以在联合中存储指向您想要的任何对象的指针。
    【解决方案6】:

    最简单的方法当然是定义一个结构或类,其中包含您希望存储的每种类型的成员。 Josh's answer 建议 Boost.Any,它几乎可以容纳 任何东西。如果您想限制值仅限于intdoublestd::string 类型的值,那么更好的选择是Boost.Variant

    如果你只是不想要使用 Boost,那么我建议你克服你的障碍并继续使用它。 “不是在这里发明的”是一种自我毁灭的政策。但是如果您不能使用 Boost,那么您可以编写自己的变体类。几年前,Andrei Alexandrescu 写了一个由三部分组成的系列(part 1part 2part 3),它的设计启发了 Boost 使用的那个。

    【讨论】:

      【解决方案7】:

      如果您需要存储的项目数量有限,请将它们放在一个类或结构中。

      如果您需要存储在此容器中的项目没有限制,那么请考虑另一种处理方式,因为唯一的方式是将它们存储为一个对象,然后将它们转换为自己的在需要访问它们时键入。

      但是,如果任何项目都可能在容器中,那么您无法知道容器中的特定项目是什么类型,因此无法转换它们。

      如果 C++ 包含反射,可能会有办法做到这一点,但 C++ 没有反射。

      【讨论】:

      • 它确实有运行时类型信息(必须启用)、type_info、typeid 等。有了它和模板,你可以做一些非常有趣的事情,如果你知道你在做什么。
      【解决方案8】:

      我对这个问题的看法不是我希望的。我认为您想要的是一个存储多种值类型的容器,您可以随意访问。

      然而,这样一来,容器必须指定它所保存的值,因此您可以拥有一个包含 500 种数据类型的类,并为每种数据类型提供一个相关的构造函数,但是,这将是超级内存效率低下的。

      这是我提出的建议,我已经研究了一天,希望它符合您的标准:

      #include <iostream>
      #include <vector>
      
      using namespace std;
      
      enum class type: unsigned int {int_t, unsigned_int_t, string_t, double_t, float_t, bool_t, unipointer_t, vector_int_t, vector_unipointer_t};//just add item types here and in the switch statement to hold more void_ps in unipointer...
      
      class unipointer {
          void* obj;//the pointer to the data. any type of pointer.
          type objtype;//the object type, kept as an enum class.
          struct void_p {//template magic... ;D
              void* void_ptr;
              template<typename T>//when object is initialized, it converts the the void* pointer to the output value.
              operator T() {
                  return reinterpret_cast<T&>(void_ptr);
              }
              void_p(void* val): void_ptr(val) {};
          };
      public:
          unipointer(void_p ptr, type ptrtype) : obj(ptr), objtype(ptrtype) {}
      
          type get_type(void) {//Once you call this function, you know the type of data stored, and can call other functions accordingly.
              return objtype;
          }
          template<typename T>//With a temlate, get any value through a pointer to it.
          T get_ptr(void){
              return reinterpret_cast<T&>(obj);
          }
          template<typename T>//With a temlate, get any value, as an object
          T get_object(void) {
              return *get_ptr<T*>();
          }
          void_p get_auto_pointer(void) {//get any pointer to value, can't be assigned to "auto*"!
              return unipointer::void_p(obj);
          }
          void_p get_auto_object(void) {//get any value, can't be assigned to "auto"!
              return *(void_p*)get_auto_pointer();
          }
      };
      
      void process_stuff(unipointer& thing, unsigned int num_of_tabs);
      
      int main() {
          double initialization = 1.2345;
          float even_another = 3.14159f;
          unipointer items(new vector<unipointer>{//one thicc object instance
              //Initialization examles:
              unipointer(new int(-12345), type::int_t),
              unipointer(new unsigned int(4'294'967'295), type::unsigned_int_t),
              unipointer(new string("That is how I store my items."), type::string_t),
              unipointer(&initialization, type::double_t),
              unipointer(&even_another, type::float_t),
              unipointer(new bool(1), type::bool_t),
              unipointer(new unipointer(new unipointer(new unipointer(new string("OMG! NESTING!"), type::string_t), type::unipointer_t), type::unipointer_t), type::unipointer_t),
              unipointer(new vector<int>{ 1,2,3 }, type::vector_int_t),
              unipointer(new vector<unipointer>{
                  unipointer(new string("That is how I store my nested items."), type::string_t),
                  unipointer(new vector<int>{4,5,6}, type::vector_int_t),
                  unipointer(new string("Is your head brimming with ideas yet?"), type::string_t)
              } , type::vector_unipointer_t)
          }, type::vector_unipointer_t);
      
          cout << "What is in the \"items\" unipointer:" << endl;
          process_stuff(items, 1);
          system("pause");
      }
      
      void process_stuff(unipointer& thing, unsigned int num_of_tabs) {
          //declare variables & lamda for interpretaion methods, using variable assignment with "get auto object/pointer"
          unsigned int* test = 0;
          double test_2 = 0;
          auto tab_to_current = [num_of_tabs]() {
              for (unsigned int i = 0; i < num_of_tabs; ++i) {
                  cout << "\t";
              }
          };
          //format the thing.
          tab_to_current();
          //look through and do stuff
          switch (thing.get_type()) {//just add item types here and in the enum class to hold more void_ps in unipointer...
          case type::int_t:
              cout << "The integer: " << *thing.get_ptr<int*>() << "." << endl;//one way of getting object back from class
              break;
          case type::string_t:
              cout << "The string: \"" << thing.get_object<string>() << "\"." << endl;//another way
              break;
          case type::unsigned_int_t:
              test = thing.get_auto_pointer();//another way
              cout << "The unsigned integer: " << *test << "." << endl;//don't forget to de-reference it!
              delete test;
              break;
          case type::double_t:
              test_2 = thing.get_auto_object();
              cout << "The double: " << test_2 << "." << endl;//even another way!
              break;
          case type::float_t:
              cout << "The float: " << float(thing.get_auto_object()) << "." << endl;//even another way!
              break;
          case type::bool_t:
              cout << "The boolean: " << *(bool*)thing.get_auto_pointer() << "." << endl;//even another way!
              break;
          case type::unipointer_t:
              cout << "A unipointer, and in it:" << endl;
              process_stuff(*&thing.get_object<unipointer>(), num_of_tabs+1);
              tab_to_current();
              cout << "[End of unipointer]" << endl;
              break;
          case type::vector_int_t:
              cout << "A vector of integers, and in it:" << endl;
              for (unsigned int i = 0; i < thing.get_object<vector<int>>().size(); ++i) {
                  tab_to_current();
                  cout << "\tItem " << i << ": " << thing.get_object<vector<int>>().at(i) << endl;
              }
              tab_to_current();
              cout << "[End of vector of integers]" << endl;
              break;
          case type::vector_unipointer_t:
              cout << "A vector of unipointers, and in it:" << endl;
              for (unsigned int i = 0; i < thing.get_object<vector<unipointer>>().size(); ++i) {
                  process_stuff(*&thing.get_object<vector<unipointer>>().at(i), num_of_tabs + 1);
              }
              tab_to_current();
              cout << "[End of unipointer vector]" << endl;
              break;
          }
      }
      

      “unipointer”类应该用一个指向任何对象类型的指针来初始化,还有对象的类型。该类可以通过函数返回您的数据,尽管它不是很安全,并且可能使用错误的数据类型调用。

      这只是一个可行的例子,我当然希望你能从中得到启发。

      而且,要回答您最初的问题,您需要设置一个列表或具有以下格式的向量:

      vector/list:
      |
      |unipointer(*double)
      |
      |unipointer(*int)
      |
      |unipointer(*string)
      |
      ...
      |
      end
      

      PS:我是对象和模板的初学者,所以这可能会很乱。许多道歉。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-30
        • 1970-01-01
        • 2018-07-20
        • 2021-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多