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

Proper way/pattern to initialize objects in a container

本文关键字:方式 模式 对象 初始化      更新时间:2023-10-16

我有一个模板化容器World,其中包含Objects。World是在Objects上执行的工作流。

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

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

建议的解决方案1

现在,我已经创建了一种Init对象,它包含初始化Object所需的值,在main.cpp中我设置了它,在Object的构造函数中我将它传递给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

我想我可以通过一些World::init_objects方法传递构造Object所需的参数。我相信这是可能的,因为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实例。

第二季度。这种方法可行吗?或者有什么主要缺点吗?(我想我更喜欢它,但也许这是一个很快就会碰壁的坏主意。)

第三季度。有没有更好的方法,我没有想过。也许这是一个简单的设计模式/实现问题。

我认为我提出的解决方案并不是相互排斥的,但我想清理我混乱的代码,并选择一个好的、干净的、好的代码实践来学习和坚持它

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(您可以注入初始化或其他任何东西)。

顺便说一句,还可以考虑使用工厂函数(针对"世界",而不是内部对象),基于已构建的对象向量创建"世界"对象。这将消除对对象的额外初始化的需要(即,它不会影响世界的公共接口),并将世界变成一个异常安全的对象(出于其他目的,它可能仍然需要上面的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)};
}

您在第一个示例中所做的基本上是创建一个Factory类。在您的案例中,它是一个工厂结构,但基本上它有相同的用途,即它是一种知道如何构建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() );
}
}

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

就其他方法而言,我想你也可以注入某种函子来处理构建,但基本上所有这些方法都有相同的基本原理,从使用对象的类中删除如何构建的逻辑。一旦实现了这种分离,最重要的是代码是清晰的和可维护的。