正确的方式/模式来初始化容器中的对象

时间:2014-02-17 13:09:27

标签: c++ design-patterns c++11 initialization

我有一个模板化容器World,它包含Object个。 World是在Object上执行的工作流程。

通常我使用默认构造函数构造Object,但现在我需要从运行时提供一些参数。

我的问题是找到一种干净利落的方法,使World用给定的值初始化Object

提议的解决方案1 ​​

现在我已经创建了一种Init对象,它包含初始化Object所需的值,在main.cpp我设置它并在Object的构造函数中{1}}我将它传递给Init的实例以执行初始化。它看起来像这样:

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);
 }
};

这种方式World不需要了解Object的构造,只需使用默认构造函数。

我不知何故不喜欢这个解决方案过于复杂,我正在寻找更好的东西。

Q1 即可。这是一个很好的解决方案,并且它有一个名称(也许它是一个设计模式,或反模式)?

提议的解决方案2

我想我可以通过一些Object方法传递构建World::init_objects所需的参数。我相信这是可能的,因为c ++ 11。 init_objects方法可以是构造对象的可变参数模板。

提议的例子:

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) );
  }
}

这样我没有额外的对象,World也不需要了解Object内部结构。 main.cpp需要致电init_objects而不是设置Object::init实例。

Q2 即可。这种方法是否可行,或有任何重大缺点? (我想我更喜欢它,但也许很快就会碰壁。)

Q3 即可。是否有更好的方法,我没有想到。也许这是一个设计模式/实现的简单问题。

我认为我提出的解决方案不是互相排斥的,但我想清理我的杂乱代码,并选择一个好的,干净的,漂亮的代码练习来接受并坚持下去。

2 个答案:

答案 0 :(得分:1)

你在第一个例子中所做的基本上是制作工厂类。在你的情况下,它是一个工厂结构,但基本上它服务于相同的目的,即它是一个知道如何构建Object的类。尽管(并且没有必要)类本身依赖于它自己的工厂是不寻常的。更常见的用途是将具体类型的工厂注入World。这可以是传统注射(作为参数)或模板注射。

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() );
  }
}

所有这些说我觉得你的可变参数模板解决方案看起来很有趣。我能看到的唯一缺点是错误代码可能很难理解。如果有人将错误的类型传递给模板,可能会导致模糊的错误。不过只有其他与模板相关的错误。

就其他方法而言,我认为你也可以注入某种类型的仿函数来处理构建,但基本上所有这些方法都具有相同的基本原理,以消除如何构建的逻辑来自使用该对象的类。 一旦你有这种分离,最重要的是你的代码清晰可维护。

答案 1 :(得分:1)

  

Q1。这是一个很好的解决方案,并且它有一个名称(可能是设计模式,还是反模式)?

是。这是(或多或少)工厂对象模式(尽管在工厂对象实现的规范示例中,对象不“知道”工厂是什么 - 对象中没有对工厂的静态引用。)

  

Q2。这种方法是否可行,或有任何重大缺点? (我想我更喜欢它,但也许很快就会碰壁。)

解决方案是可行的。一个缺点是您在对象中设置的参数/值上实现模板的方式。

请考虑这个实现:

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
    }
};

客户代码:

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

使用此解决方案,您可以以模块化方式使用apply(您可以实际注入初始化或其他任何内容)。

作为旁注,还要考虑使用工厂函数(对于World,而不是对于里面的对象),基于已构造的对象向量创建World对象。这将消除对对象的额外初始化的需要(即,它不会影响世界的公共接口)并将World转换为异常安全对象(可能仍需要上面的apply方法,用于其他目的) :

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)};
}

编辑:具体的make_world示例,不需要封装对象的默认构造函数:

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)};
}