如何在C ++容器中存储不同类型的对象?

时间:2011-01-19 17:14:55

标签: c++ types polymorphism containers

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

int x;
string y;
double z;

我受格式限制:

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

强制mycontainer只包含一种类型。

在任何人提出泛型之前,由于C ++ 附带的标准vectorlist容器已经是通用,因此它们无法正常工作 - 它们可以是任何容器类型但不能包含多种类型。

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

[编辑] 嘿伙计,非常感谢你的建议 - 我应该解释一下我将如何使用这个容器,但它有点复杂,因此上面的(大)简化。我认为这里最好的选择是使用Boost。再次感谢。

8 个答案:

答案 0 :(得分:21)

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

如果你想做一些快速而又脏的事情,可以创建一个结构或者一个包含所有潜在类型成员的联合,以及一个枚举或其他类型在对象中“活动”的指示符。尤其要小心工会,因为它们有一些有趣的属性(例如,如果你读错了工会成员就调用了未定义的行为,一次只能有一个成员'活动',最近被写入的那个)。 / p>

我很好奇你正在做什么,你需要这样一个结构。

答案 1 :(得分:13)

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

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

答案 2 :(得分:6)

您想要的是一个“异构容器”。 C ++在STL中不支持技术,但Boost确实支持它们。

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

答案 3 :(得分:2)

您可以使用结构,类或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)));

答案 4 :(得分:0)

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

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

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

struct Data
{
    enum DATATYPE { DT_INT, DT_STRING, DT_DOUBLE } type;

    int intVal;
    std::string stringVal;
    double doubleVal;
};

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

答案 5 :(得分:0)

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

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

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

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

答案 6 :(得分:0)

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

如果你只是不想使用Boost,那么我建议你克服你的挂机并使用它。 “不在这里发明”是一种自我毁灭的政策。但是如果你不能使用Boost,那么你可以编写自己的变体类。几年前,安德烈·亚历山大夫斯基(Andrei Alexandrescu)撰写了一个由三部分组成的系列文章(part 1part 2part 3),其设计启发了Boost的一个用途。

答案 7 :(得分:0)

我对这个问题所拥有的不是我希望能起作用的。我认为您想要的是一个可以存储多个值类型的容器,您可以随意访问它。

但是,这样一来,容器将必须指定其持有的值,因此您可能要在其中包含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:我是对象和模板的初学者,所以这可能很混乱。很多策略。