C++ One std::vector containing template class of multiple types

前端 未结 5 765
太阳男子
太阳男子 2021-01-31 02:03

I need to store multiple types of a template class in a single vector.

Eg, for:

template 
class templateClass{
     bool someFunction()         


        
相关标签:
5条回答
  • 2021-01-31 02:29

    The solutions given so far are fine though be aware that in case you were returning the template type other than bool in your example , none of these would help as the vtable slots would not be able to be measured before hand. There are actually limits , from a design point of view , for using a template oriented polymorphic solution.

    0 讨论(0)
  • 2021-01-31 02:31

    Solution nr. 1

    This solution inspired by Sean Parent's C++ Seasoning talk. I highly recommend to check it out on youtube. My solution simplified a bit and the key is to store object in method itself.

    One method only

    Create a class that will invoke method of stored object.

    struct object {
        template <class T>
        object(T t)
        : someFunction([t = std::move(t)]() { return t.someFunction(); })
        { }
    
        std::function<bool()> someFunction;
    };
    

    Then use it like this

    std::vector<object> v;
    
    // Add classes that has 'bool someFunction()' method
    v.emplace_back(someClass());
    v.emplace_back(someOtherClass());
    
    // Test our vector
    for (auto& x : v)
        std::cout << x.someFunction() << std::endl;
    

    Several methods

    For several methods use shared pointer to share object between methods

    struct object {
        template <class T>
        object(T&& t) {
            auto ptr = std::make_shared<std::remove_reference_t<T>>(std::forward<T>(t));
            someFunction = [ptr]() { return ptr->someFunction(); };
            someOtherFunction = [ptr](int x) { ptr->someOtherFunction(x); };
        }
    
        std::function<bool()> someFunction;
        std::function<void(int)> someOtherFunction;
    };
    

    Other types

    Primitive types (such as int, float, const char*) or classes (std::string etc.) may be wrapped in the same way as object class do but behave differently. For example:

    struct otherType {
        template <class T>
        otherType(T t)
        : someFunction([t = std::move(t)]() {
                // Return something different
                return true;
            })
        { }
    
        std::function<bool()> someFunction;
    };
    

    So now it is possible to add types that does not have someFunction method.

    v.emplace_back(otherType(17));      // Adding an int
    v.emplace_back(otherType("test"));  // A string
    

    Solution nr. 2

    After some thoughts what we basically done in first solution is created array of callable functions. So why not just do the following instead.

    // Example class with method we want to put in array
    struct myclass {
        void draw() const {
            std::cout << "myclass" << std::endl;
        }
    };
    
    // All other type's behaviour
    template <class T>
    void draw(const T& x) {
        std::cout << typeid(T).name() << ": " << x << std::endl;
    }
    
    int main()
    {
        myclass x;
        int y = 17;
    
        std::vector<std::function<void()>> v;
    
        v.emplace_back(std::bind(&myclass::draw, &x));
        v.emplace_back(std::bind(draw<int>, y));
    
        for (auto& fn : v)
            fn();
    }
    

    Conclusion

    Solution nr. 1 is definitely an interesting method that does not require inheritance nor virtual functions. And can be used to other stuff where you need to store a template argument to be used later.

    Solution nr. 2, on the other hand, is simpler, more flexible and probably a better choice here.

    0 讨论(0)
  • 2021-01-31 02:40

    If you're looking at a container to store multiple types, then you should explore boost variant from the popular boost library.

    0 讨论(0)
  • 2021-01-31 02:41

    Why your code doesn't work:

    Calling a virtual function on a value doesn't use polymorphism. It calls the function which is defined for the type of this exact symbol as seen by the compiler, not the runtime type. When you insert sub types into a vector of the base type, your values will be converted into the base type ("type slicing"), which is not what you want. Calling functions on them will now call the function as defined for the base type, since not it is of that type.

    How to fix this?

    The same problem can be reproduced with this code snippet:

    templateInterface x = templateClass<int>(); // Type slicing takes place!
    x.someFunction();  // -> templateInterface::someFunction() is called!
    

    Polymorphism only works on a pointer or reference type. It will then use the runtime type of the object behind the pointer / reference to decide which implementation to call (by using it's vtable).

    Converting pointers is totally "safe" with regard to type slicing. Your actual values won't be converted at all and polymorphism will work as expected.

    Example, analogous to the code snippet above:

    templateInterface *x = new templateClass<int>();  // No type slicing takes place
    x->someFunction();  // -> templateClass<int>::someFunction() is called!
    
    delete x;  // Don't forget to destroy your objects.
    

    What about vectors?

    So you have to adopt these changes in your code. You can simply store pointers to actual types in the vector, instead of storing the values directly.

    When working with pointers you also have to care about deleting your allocated objects. For this you can use smart pointers which care about deletion automatically. unique_ptr is one such smart pointer type. It deletes the pointee whenever it goes out of scope ("unique ownership" - the scope being the owner). Assuming the lifetime of your objects is bound to the scope this is what you should use:

    std::vector<std::unique_ptr<templateInterface>> v;
    
    templateClass<int> *i = new templateClass<int>();    // create new object
    v.push_back(std::unique_ptr<templateInterface>(i));  // put it in the vector
    
    v.emplace_back(new templateClass<int>());   // "direct" alternative
    

    Then, call a virtual function on one of these elements with the following syntax:

    v[0]->someFunction();
    

    Make sure you make all functions virtual which should be possible to be overridden by subclasses. Otherwise their overridden version will not be called. But since you already introduced an "interface", I'm sure you are working with abstract functions.

    Alternative approaches:

    Alternative ways to do what you want is to use a variant type in the vector. There are some implementations of variant types, the Boost.Variant being a very popular one. This approach is especially nice if you don't have a type hierarchy (for example when you store primitive types). You would then use a vector type like std::vector<boost::variant<int, char, bool>>

    0 讨论(0)
  • 2021-01-31 02:50

    Polymorphism only works through pointers or references. You'll need the non-template base. Beyond that, you'll need to decide where the actual objects in container will live. If they're all static objects (with sufficient lifetime), just using a std::vector<TemplateInterface*>, and inserting with v.push_back(&t1);, etc., should do the trick. Otherwise, you'll probably want to support cloning, and keep clones in the vector: preferably with Boost pointer containers, but std::shared_ptr can be used as well.

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