luk32
luk32

Reputation: 16080

Proper way/pattern to initialize objects in a container

I have a templated container World, that holds Objects. World is a workflow executed on Objects.

Normally I constructed Objects with the default constructor, but now I need to provide some parameters from the run-time.

My problem is finding a good and clean way to make World initialize Objects with given values.

Proposed solution 1

For now I have created a kind of Init object, that holds values needed for initialization of an Object, in the main.cpp I set it up and within the constructor of the Object I pass it to the instance of Init to perform initialization. It look something like this:

Object.h:

class Object; //forward declaration
struct Init {
  void Initialize(Object& object);
  int property1;
  double property2;
  string property3;
};

class Object{
 static Init init;
public:
 Object(){
   init.Initialize(*this);
 }
};

This way World does not need to have any knowledge on the construction of the Object and can just use a default constructor.

I somehow dislike this solution as overly complex and I am looking for something better.

Q1. Is this a good solution, and does this have a name (perhaps it's a design pattern, or antipattern)?

Proposed solution 2

I was thinking that I could pass-through the parameters needed to construct an Object, through some World::init_objects method. I believe this is possible since c++11. The init_objects method could be a variadic template that constructs the objects.

Proposed example:

template<typename Object>
class World {
  std::vector<Object> _internal_container;
  size_t _size;
public:
  World(size_t size) : _size(size) { _internal_container.reserve(_size); };

  template<typename... Args>
  init_objects(const Args&... args){
    for(size_t i = 0; i < _size; ++i) _internal_container.emplace_back( Object(args) );
  }
}

This way I don't have an extra object, and World also does not need to know anything about Object internal structure. main.cpp needs to call init_objects instead of setting up the Object::init instance.

Q2. Is such approach feasible, or has any major drawbacks? (I think I like it better, but maybe it is a bad idea that will hit a wall soon.)

Q3. Is there a better way to do it, that I did not think of. Maybe it is a simple problem with a go-to design pattern/implementation.

I think that my proposed solutions are not mutually exclusive, but I want to clean up my messy code, and choose a good, clean and nice code practice to pick up and stick to it.

Upvotes: 2

Views: 1262

Answers (2)

utnapistim
utnapistim

Reputation: 27385

Q1. Is this a good solution, and does this have a name (perhaps it's a design pattern, or antipattern)?

Yes. This is (more or less) the factory object pattern (though in the canonical example of Factory Object implementation, objects do not "know" what a factory is - there is no static reference to the factory in objects).

Q2. Is such approach feasible, or has any major drawbacks? (I think I like it better, but maybe it is a bad idea that will hit a wall soon.)

The solution is feasible. One drawback is the way you implement the template on arguments/values to be set in the object.

Consider this implementation instead:

template<typename Object>
class World {
    std::vector<Object> objects;
    // size_t _size; // << size removed
public:
    World(size_t size) : objects(size) // use default constructor 
    {}

    template<typename P> // << changed here
    void apply(P predicate) { // << change here
        std::for_each(objects.begin(), objects.end(), predicate); // << and here
    }
};

Client code:

World w{10}; // create 10 objects with default values
w.apply([](object& o) { o.set_xyz(10); o.set_yyy(20); });

With this solution, you can use apply in a modular way (you can inject initialization or anything else really).

As a side note, also consider creating a World object based on an already constructed vector of objects, using a factory function (for the World, not for the objects inside). This would eliminate the need for extra initialization of objects (i.e. it would not affect the public interface of the world) and turn World into an exception-safe object (which may still require the apply method above, for other purposes):

template<typename O> class World {
    std::vector<O> objects;
public:
    World(std::vector<O> obj) : objects{std::move(obj)} {}
    // eventually define apply function here
};

template<typename O, typename... Args>
World<O> make_world(size_t size, const Args&... args){
    std::vector<O> objects{size};
    for(auto& o: objects)
    { /* set arguments here */ }
    return World<O>{std::move(objects)};
}

Edit: Concrete make_world example, without requiring default constructor for the encapsulated objects:

struct Length { int length; Length(int l) : length{l} {} };

World<Length> make_length_world(size_t size, int default_length)
{
    std::vector<Length> values;
    for(size_t index = 0; index < size; ++index)
        values.emplace_back(default_length);
    return World<Length>{std::move(values)};
}

struct Person {
    std::string first_name; std::string last_name;
    Person(std::string first, std::string last)
    : first_name{std::move(first)}, last_name{std::move(last)} {}
};


World<Person> make_robert_paulson_world(size_t size)
    // "his name is Robert Paulson"
    // so don't pass name as parameter
{
    std::vector<Person> values;
    for(size_t index = 0; index < size; ++index)
        values.emplace_back("Robert", "Paulson");
    return World<Person>{std::move(values)};
}

Upvotes: 1

Dennis
Dennis

Reputation: 3731

What you are doing in your first example is basically making a Factory Class. In your case it is a factory struct but basically it serves the same purpose, i.e. it is a class that knows how to build Object. It is unusual though (and unnecessary) for the class itself to have a dependency on its own factory. A more usual use would be for you to inject a concrete type of the factory into World. This can be either a traditional injection (as a parameter) or a template injection.

template<typename FactoryType>
class World {
private:
  std::vector<Object> _internal_container;
public:
  World(size_t objectCount){
    FactoryType factory; // of course you could store this in a field if you need.
    for(size_t i = 0; i < objectCount; ++i) 
      _internal_container.emplace_back( factory.getDefaultObject() );
  }
}

All that said I think that your variadic template solution looks interesting. The only downside I can see is that the error codes might be hard to understand. If someone passes the wrong types to the template it could result in an obscure error. No more than other template related errors though.

In terms of other methods of doing this, I suppose you could also inject a functor of some kind to deal with the building, but basically all of these methods all have the same underlying principle, to remove the logic of how to build from the class USING the object. The most important thing once you have this separation is that your code is clear and maintainable.

Upvotes: 1

Related Questions