Storing a list of arbitrary objects in C++

前端 未结 13 2023
野趣味
野趣味 2021-02-06 08:29

In Java, you can have a List of Objects. You can add objects of multiple types, then retrieve them, check their type, and perform the appropriate action for that type.
For e

相关标签:
13条回答
  • 2021-02-06 09:08

    Your example using Boost.Variant and a visitor:

    #include <string>
    #include <list>
    #include <boost/variant.hpp>
    #include <boost/foreach.hpp>
    
    using namespace std;
    using namespace boost;
    
    typedef variant<string, int, bool> object;
    
    struct vis : public static_visitor<>
    {
        void operator() (string s) const { /* do string stuff */ }
        void operator() (int i) const { /* do int stuff */ }
        void operator() (bool b) const { /* do bool stuff */ }      
    };
    
    int main() 
    {
        list<object> List;
    
        List.push_back("Hello World!");
        List.push_back(7);
        List.push_back(true);
    
        BOOST_FOREACH (object& o, List) {
            apply_visitor(vis(), o);
        }
    
        return 0;
    }
    

    One good thing about using this technique is that if, later on, you add another type to the variant and you forget to modify a visitor to include that type, it will not compile. You have to support every possible case. Whereas, if you use a switch or cascading if statements, it's easy to forget to make the change everywhere and introduce a bug.

    0 讨论(0)
  • 2021-02-06 09:11

    boost::variant is similar to dirkgently's suggestion of boost::any, but supports the Visitor pattern, meaning it's easier to add type-specific code later. Also, it allocates values on the stack rather than using dynamic allocation, leading to slightly more efficient code.

    EDIT: As litb points out in the comments, using variant instead of any means you can only hold values from one of a prespecified list of types. This is often a strength, though it might be a weakness in the asker's case.

    Here is an example (not using the Visitor pattern though):

    #include <vector>
    #include <string>
    #include <boost/variant.hpp>
    
    using namespace std;
    using namespace boost;
    
    ...
    
    vector<variant<int, string, bool> > v;
    
    for (int i = 0; i < v.size(); ++i) {
        if (int* pi = get<int>(v[i])) {
            // Do stuff with *pi
        } else if (string* si = get<string>(v[i])) {
            // Do stuff with *si
        } else if (bool* bi = get<bool>(v[i])) {
            // Do stuff with *bi
        }
    }
    

    (And yes, you should technically use vector<T>::size_type instead of int for i's type, and you should technically use vector<T>::iterator instead anyway, but I'm trying to keep it simple.)

    0 讨论(0)
  • 2021-02-06 09:12

    Just for completeness of this topic I want to mention that you can actually do this with pure C by using void* and then casting it into whatever it has to be (ok, my example isn't pure C since it uses vectors but that saves me some code). This will work if you know what type your objects are, or if you store a field somewhere which remembers that. You most certainly DON'T want to do this but here is an example to show that it's possible:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    int main() {
    
      int a = 4;
      string str = "hello";
    
      vector<void*> list;
      list.push_back( (void*) &a );
      list.push_back( (void*) &str );
    
      cout <<  * (int*) list[0] << "\t" << * (string*) list[1] << endl;
    
      return 0;
    }
    
    0 讨论(0)
  • 2021-02-06 09:15

    C++ does not support heterogenous containers.

    If you are not going to use boost the hack is to create a dummy class and have all the different classes derive from this dummy class. Create a container of your choice to hold dummy class objects and you are ready to go.

    class Dummy {
       virtual void whoami() = 0;
    };
    
    class Lizard : public Dummy {
       virtual void whoami() { std::cout << "I'm a lizard!\n"; }
    };
    
    
    class Transporter : public Dummy {
       virtual void whoami() { std::cout << "I'm Jason Statham!\n"; }
    };
    
    int main() {
       std::list<Dummy*> hateList;
       hateList.insert(new Transporter());
       hateList.insert(new Lizard());
    
       std::for_each(hateList.begin(), hateList.end(), 
                     std::mem_fun(&Dummy::whoami));
       // yes, I'm leaking memory, but that's besides the point
    }
    

    If you are going to use boost you can try boost::any. Here is an example of using boost::any.

    You may find this excellent article by two leading C++ experts of interest.

    Now, boost::variant is another thing to look out for as j_random_hacker mentioned. So, here's a comparison to get a fair idea of what to use.

    With a boost::variant the code above would look something like this:

    class Lizard {
       void whoami() { std::cout << "I'm a lizard!\n"; }
    };
    
    class Transporter {
       void whoami() { std::cout << "I'm Jason Statham!\n"; }
    };
    
    int main() {
    
       std::vector< boost::variant<Lizard, Transporter> > hateList;
    
       hateList.push_back(Lizard());
       hateList.push_back(Transporter());
    
       std::for_each(hateList.begin(), hateList.end(), std::mem_fun(&Dummy::whoami));
    }
    
    0 讨论(0)
  • 2021-02-06 09:17

    How often is that sort of thing actually useful? I've been programming in C++ for quite a few years, on different projects, and have never actually wanted a heterogenous container. It may be common in Java for some reason (I have much less Java experience), but for any given use of it in a Java project there might be a way to do something different that will work better in C++.

    C++ has a heavier emphasis on type safety than Java, and this is very type-unsafe.

    That said, if the objects have nothing in common, why are you storing them together?

    If they do have things in common, you can make a class for them to inherit from; alternately, use boost::any. If they inherit, have virtual functions to call, or use dynamic_cast<> if you really have to.

    0 讨论(0)
  • 2021-02-06 09:17

    While you cannot store primitive types in containers, you can create primitive type wrapper classes which will be similar to Java's autoboxed primitive types (in your example the primitive typed literals are actually being autoboxed); instances of which appear in C++ code (and can (almost) be used) just like primitive variables/data members.

    See Object Wrappers for the Built-In Types from Data Structures and Algorithms with Object-Oriented Design Patterns in C++.

    With the wrapped object you can use the c++ typeid() operator to compare the type. I am pretty sure the following comparison will work: if (typeid(o) == typeid(Int)) [where Int would be the wrapped class for the int primitive type, etc...] (otherwise simply add a function to your primitive wrappers that returns a typeid and thus: if (o.get_typeid() == typeid(Int)) ...

    That being said, with respect to your example, this has code smell to me. Unless this is the only place where you are checking the type of the object, I would be inclined to use polymorphism (especially if you have other methods/functions specific with respect to type). In this case I would use the primitive wrappers adding an interfaced class declaring the deferred method (for doing 'do stuff') that would be implemented by each of your wrapped primitive classes. With this you would be able to use your container iterator and eliminate your if statement (again, if you only have this one comparison of type, setting up the deferred method using polymorphism just for this would be overkill).

    0 讨论(0)
提交回复
热议问题