2

I'm trying to declare a lot of templatized Derived<T> objects that inherit from Base, and pushing them back in a std::vector<Base*>.

struct Base { ... };
template<typename T> struct Derived : Base { /* ctor(const string&) */ ... }

Derived<bool> online{"online"};
Derived<bool> official{"official"};
Derived<bool> noRotation{"no_rotation"};
Derived<bool> noBackground{"no_background"};
Derived<int> noSound{"no_sound"};
Derived<string> noMusic{"no_music"};
Derived<bool> blackAndWhite{"black_and_white"};

vector<Base*> configValues{&online, 
                           &official, 
                           &noRotation, 
                           &noBackground, 
                           &noSound, 
                           &noMusic, 
                           &blackAndWhite};

As you can see, the code is horrible. Is there any way to automate this without passing the vector as a const& in the Derived<T>::Derived<T>(...) constructor?

By automate I mean avoiding the repetition of the objects' names. I would like to fill a std::vector with all my Derived<T> objects without having to list them all manually.

(Macros accepted, but hopefully there's a better solution)

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • could you use `std::vector>` instead? – justin Jun 22 '13 at 22:04
  • What do you mean by "automate"? – tmpearce Jun 22 '13 at 22:04
  • 1
    @justin: `T` is not always `bool` in my application. Sorry that the example didn't show that. @tmpearce: I mean avoiding the repetition of the objects' names. I would like to fill a vector with all my `Derived` objects without having to list them all manually. – Vittorio Romeo Jun 22 '13 at 22:06
  • What are you trying to accomplish? It seems like you might be interested in `boost::any`. – Tim Jun 23 '13 at 09:44

4 Answers4

1

There seems to be two separate questions here...

The first question appears be that you do not want to pass a constant std::string to the constructor of Derived<T>. The only reason I can think of to do this is if the string needs to be modified while constructing a Derived<T> object. If you do not need to modify the string I recommend taking it by const reference like you are currently doing.

If you do need to modify the string in the constructor you can change the parameter to pass the string by either rvalue reference or by value.

Derived(std::string&& str) { /*...*/ } // Pass by r-value reference
Derived(std::string str) { /*...*/ }   // Pass by value

Both will allow you to modify the string during construction.


As for the second question in you comment...

To fill the vector in the way described in your comment you can use uniform initialization. The only caveat is that you will need to use dynamic storage duration for the objects added to the vector.

std::vector<Base*> configValues{
    {
    new Derived<bool>{"online"},
    new Derived<bool>{"official"},
    new Derived<bool>{"no_rotation"},
    new Derived<bool>{"no_background"},
    new Derived<int>{"no_sound"},
    new Derived<std::string>{"no_music"},
    new Derived<bool>{"black_and_white"}
    }
};

I'm not sure what your objective is so how you decide to manage the lifetime of those objects is up to you. I highly recommend using a smart pointer to manage their lifetime and ownership. For instance if you require shared ownership you could use std::shared_ptr like in the following example.

std::vector<std::shared_ptr<Base>> configValues{
    {
    std::shared_ptr<Base>(new Derived<bool>{"online"}),
    std::shared_ptr<Base>(new Derived<bool>{"official"}),
    std::shared_ptr<Base>(new Derived<bool>{"no_rotation"}),
    std::shared_ptr<Base>(new Derived<bool>{"no_background"}),
    std::shared_ptr<Base>(new Derived<int>{"no_sound"}),
    std::shared_ptr<Base>(new Derived<std::string>{"no_music"}),
    std::shared_ptr<Base>(new Derived<bool>{"black_and_white"})
    }
};

Since std::initializer_list requires objects to be copyable you cannot use std::unique_ptr to manage the lifetime of the objects.

Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
1

So lifetime is going to be one of the problems you face, and how this pattern is used in your program is another influence to the solution.

So supposing your Derived<> instances are not owned by the vector, then you need to ensure they outlive it. There are three basic approaches, which could be combined in some cases.

The first: Create a class which stores and populates the vector. If you have the same set of Derived<> types or parameters duplicated, this can at least 'deduplicate' your common structures. Then you can give those member functions to return or populate the vectors.

The second is to use std::tuple. Tuples could be useful if there are many parameter list variants and you want one instance to store all those Derived instances while having a way to create common routines (like filling the vector):

typedef std::tuple<
    Derived<bool>,
    Derived<bool>,
    Derived<bool>,
    Derived<bool>,
    Derived<bool>,
    Derived<int>,
    Derived<std::string>,
    Derived<bool>
> atuple;

atuple t{
    "online",
    "official",
    "no_rotation",
    "no_background",
    "no_sound",
    "no_music",
    "black_and_white"
};
const size_t size(std::tuple_size<atuple>::value);
/* or you could use std::array because the size is constant. */
std::vector<Base*>configValues;
configValues.reserve(size);
push(t,configValues);

And push() would look like:

template<std::size_t I = 0, typename V, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
push(std::tuple<Tp...>& t, V&)
{ }

template<std::size_t I = 0, typename V, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
push(std::tuple<Tp...>& t, V& vec)
{
 vec.push_back(&std::get<I>(t));
 push<I + 1, V, Tp...>(t, vec);
}

(borrowed from iterate over tuple).

If you don't face this problem in multiple parts of your program, then these solutions will not be so useful to you.

The third - use arrays:

 std::array<Derived<bool>,5> a{{{"online"},{"official"},{"no_rotation"},{"no_background"},{"black_and_white"}}};
 Derived<int> noSound{"no_sound"};
 Derived<string> noMusic{"no_music"};
 vector<Base*> configValues{&noSound,&noMusic};
 for (Derived<bool>& b:a) configValues.push_back(&b); // original order not retained
Community
  • 1
  • 1
justin
  • 104,054
  • 14
  • 179
  • 226
1

It sounds like you're going for a factory-pattern type of implementation, with a common container that all derived objects get added to.

Here's a working example to get you started (ideone):

#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <iostream>

struct Base
{
    typedef std::shared_ptr<Base> SharedPtr;
    virtual ~Base(){}
    virtual void print(){}
    template<class T>
    static SharedPtr makeDerived(const std::string& name);

    static std::vector<SharedPtr> objs;
};
std::vector<Base::SharedPtr> Base::objs;
template<class T>
struct Derived : public Base
{
    Derived(const std::string& name):name(name){}
    void print(){std::cout<<name<<std::endl;}
    std::string name;
};

template<class T>
Base::SharedPtr Base::makeDerived(const std::string& name)
{
    SharedPtr p = std::make_shared<Derived<T> >(Derived<T>(name));
    objs.push_back(p);
    return p;
}

int main()
{
    Base::makeDerived<bool>("online");
    Base::makeDerived<int>("users");
    std::for_each(Base::objs.begin(),Base::objs.end(),
                  [](Base::SharedPtr p){p->print();}   );
}

shared_ptr ought to help with memory management. You can go with a static container or make an object to hold all your container, doesn't make a big difference.

tmpearce
  • 12,523
  • 4
  • 42
  • 60
0

Thanks for the answers, upvoted them all. In the end I decided to create a "helper" class that dealt with ownership and creation.

From my GitHub SSVUtilsJson repo:

class Manager
{
    private:
        ssvu::MemoryManager<Base> memoryManager; 
        // MemoryManager internally has a vector<Base*> of owned pointers

    public:
        template<typename T> Derived<T>& create() { return memoryManager.create<Derived<T>>(); }
        // MemoryManager::create<T> creates and stores a 'new T*' and returns a reference to it
};

Usage (from my GitHub SSVOpenHexagon repo)

Manager lvm;

auto& online                    (lvm.create<bool>());
auto& official                  (lvm.create<string>());
auto& noRotation                (lvm.create<int>());
auto& noBackground              (lvm.create<double>());
auto& noSound                   (lvm.create<char>());
auto& noMusic                   (lvm.create<void>());

for(Base* b : lvm.getItems()) { /* do something */ }
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416