在C++中动态创建对象

creating objects dynamically in C++

本文关键字:创建对象 动态 C++      更新时间:2023-10-16

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

当您使用消息传递方案(通常通过TCP/IP)时,还有另一种相关的情况,其中每个消息都是一个对象。我喜欢将该模式实现为让每个消息将自己序列化到某个序列化流接口中,该接口在发送端运行良好且相当干净,但在接收方,我总是发现自己在检查消息上的标头以确定类型,然后使用链接文章中的模式构建适当的消息对象,然后让它从流中反序列化自己。有时我会实现它,这样构造和反序列化就可以作为构造函数的一部分同时进行,这似乎更像RAII,但这对计算类型的if/else语句来说只是一个小小的安慰。

有更好的解决方案吗?如果你打算推荐一个第三方图书馆,它应该是免费的(最好是开源的),如果你能解释一下图书馆是如何实现这一壮举的,我将不胜感激。

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

这通常是我所做的。它假设有一个键给你一个类型(int标记、字符串等)。我制作了一个类,它有一个键到工厂函数的映射,还有一个注册函数,它接受一个键和工厂函数并将其添加到映射中。还有一个create函数,它获取一个键,在映射中查找它,调用factory函数,并返回创建的对象。举个例子,使用一个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
}

您在这里描述的是工厂模式。一种变体是构建器模式。

我建议阅读C++FAQ-Lite关于序列化和非序列化的问题。

其中有很多细节我无法在回答中轻松总结,但这些常见问题解答确实涵盖了创建其类型仅在运行时已知的对象。

特别是:

#36.8

尽管,在最基本的层面上,你可以实现一个类似于的工厂

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

阅读经典的"四人帮"(又名GOF)。考虑[此网站[(http://www.dofactory.com/Patterns/PatternAbstract.aspx)用于工厂和C#中的其他模式。

除非我遗漏了什么,否则您不需要static_cast来创建一个对象,该对象的运行时类型是您应该从工厂返回的类型的子类,然后以多种形式使用它:

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

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