在C ++中动态创建对象

时间:2008-10-28 13:43:35

标签: c++ serialization

我刚刚阅读了这个thread,我突然意识到,OP正在询问的模式有一种看似有效的用法。我知道我以前用它来实现对象的动态创建。据我所知,在C ++中没有更好的解决方案,但我想知道是否有任何大师知道更好的方法。通常,当我需要在编译时基于一个未知的东西(例如基于配置文件)创建对象的几个子类之一时,我会遇到这种情况。一旦创建了对象,我就会以多态方式使用它。

当您使用消息传递方案(通常通过TCP / IP)时,还有另一种相关情况,其中每条消息都是一个对象。我喜欢实现这种模式,因为让每条消息将自己序列化为一些序列化流接口,这种接口效果很好并且在发送端相当干净,但是在接收器上,我总是发现自己正在检查消息上的标头来确定类型,然后使用链接文章中的模式构建适当的消息对象,然后让它从流中反序列化。有时我实现它,以便构造和反序列化同时作为构造函数的一部分发生,这似乎更多RAII,但这对于弄清楚类型的if / else语句的混乱是一个小小的安慰。

那里有更好的解决方案吗?如果您要建议第三方库,它应该是免费的(理想情况下是开源的),如果您能解释一下该库如何实现这一壮举,我将不胜感激。

6 个答案:

答案 0 :(得分:4)

您在此处描述的内容称为factory模式。变体是builder模式。

答案 1 :(得分:4)

我认为你要问的是如何将对象创建代码与对象本身保持在一起。

这通常就是我的工作。它假定有一些键可以为您提供类型(int标记,字符串等)。我创建了一个具有工厂函数键的映射类,以及一个带有键和工厂函数并将其添加到映射的注册函数。还有一个创建函数,它接受一个键,在地图中查找它,调用工厂函数,并返回创建的对象。例如,获取一个int键和一个包含其余信息的流来构建对象。我没有测试过,甚至没有编译过这段代码,但它应该给你一个想法。

class Factory
{
    public:
    typedef Object*(*Func)(istream& is);
    static void register(int key, Func f) {m[key] = f;}
    Object* create(key, istream& is) {return m[key](is);}
    private:
    std::map<key, func> m;
}

然后在从子对象派生的每个类中,使用适当的键和工厂方法调用register()方法。

要创建对象,您只需要这样的内容:

while(cin)
{
    int key;
    is >> key;
    Object* obj = Factory::Create(key, is);
    // do something with objects
}

答案 2 :(得分:3)

我建议您阅读C++ FAQ Lite questions concerning serialisation and unserialisation

我的答案中有很多细节我无法轻易总结,但这些常见问题解答的内容包括创建只在运行时才知道其类型的对象。

特别是:

#36.8

尽管在最基本的层面上,您可以实施类似于以下的工厂:

Base* Base::get_object(std::string type)
{
    if (type == "derived1") return new Derived1;
    if (type == "derived2") return new Derived2;
}

答案 3 :(得分:0)

阅读经典Gang Of Four aka GOF。 考虑[此站点[(http://www.dofactory.com/Patterns/PatternAbstract.aspx)的工厂和C#中的其他模式。

答案 4 :(得分:0)

除非我遗漏了某些内容,否则您不需要static_cast来创建一个对象,其运行时类型是您应该从工厂返回的类型的子类,然后以多态方式使用它: / p>

class Sub1 : public Super { ... };
class Sub2 : public Super { ... };

Super *factory(int param) {
    if (param == 1) return new Sub1();
    if (param == 2) return new Sub2();
    return new Super();
}

int main(int argc, char **argv) {
    Super *parser = factory(argc);
    parser->parse(argv); // parse declared virtual in Super
    delete parser;
    return 0;
}

OP在您提到的问题中讨论的模式是从某个地方获取Super *,然后通过检查RTTI并将if / else子句用于程序员已知的所有子类来将其转换为运行时类型。这与“我在创建对象时多态使用对象”完全相反。

理论上,我反序列化的首选方法是责任链:编写能够查看序列化数据(包括类型标题)的工厂,并自行决定是否可以从中构造对象。为了提高效率,请让工厂注册他们感兴趣的类型,因此他们并不都会查看每个传入的对象,但我在此处省略了:

Super *deserialize(char *data, vector<Deserializer *> &factories>) {
    for (int i = 0; i < factories.size(); ++i) { // or a for_each loop
        Super *result = factories[i]->deserialize(data);
        if (result != NULL) return result;
    }
    throw stop_wasting_my_time_with_garbage_data();
}

在实践中,我总是经常写一个大开关,比如只有枚举类型,一些命名常量,以及构造后调用的虚拟反序列化方法:

Super *deserialize(char *data) {
    uint32_t type = *((uint32_t *)data); // or use a stream
    switch(type) {
        case 0: return new Super(data+4);
        case 1: return new Sub1(data+4);
        case 2: return new Sub2(data+4);
        default: throw stop_wasting_my_time_with_garbage_data();
    }
}

答案 5 :(得分:0)

工厂模式的基本操作是将标识符映射到特定类型的(通常是新的)实例,其中类型取决于标识符的某些属性。如果你不这样做,那么它不是工厂。其他一切都是品味问题(即性能,可维护性,可扩展性等)。