代码之家  ›  专栏  ›  技术社区  ›  rmeador

在C++中动态创建对象

  •  2
  • rmeador  · 技术社区  · 16 年前

    我只是在看这个 thread 我突然想到,有一个似乎是有效的使用的模式,该行动要求。我知道我以前用过它来实现对象的动态创建。据我所知,在C++中没有更好的解决方案,但我想知道有没有哪个专家知道更好的方法。通常,当我需要创建一个基于对象的多个子类中的一个子类时,会遇到这种情况,而这个子类在编译时是未知的(例如基于配置文件)。一旦创建了对象,我就会使用多态性。

    当您使用消息传递方案(通常通过TCP/IP)时,还有另一种相关情况,其中每个消息都是一个对象。我喜欢实现这样的模式,即让每个消息序列化到某个序列化流接口中,这个接口在发送端工作得很好并且相当干净,但是在接收端,我总是发现自己检查消息的头以确定类型,然后使用来自li的模式构造适当的消息对象。链接了文章,然后让它从流中反序列化自身。有时我会实现它,以便构造和反序列化与构造函数的一部分同时发生,这看起来更像是RAII,但这对于弄清楚类型的if/else语句的混乱来说是一个小小的安慰。

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

    6 回复  |  直到 12 年前
        1
  •  4
  •   BatchyX    16 年前

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

    这通常是我所做的。它假定有一些键为您提供了一个类型(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
  •  4
  •   Mr Fooz    16 年前

    你在这里描述的是 factory 模式。变量是 builder 模式。

        3
  •  3
  •   SpoonMeiser    16 年前

    我建议你读一下 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;
    }
    
        4
  •  0
  •   kenny    16 年前

    阅读经典 Gang Of Four aka GOF . 考虑[本网站[( http://www.dofactory.com/Patterns/PatternAbstract.aspx )对于工厂和其他C模式。

        5
  •  0
  •   Steve Jessop    16 年前

    除非我遗漏了一些东西,否则您不需要静态类型转换来创建一个运行时类型是您应该从工厂返回的类型的子类的对象,然后以多态方式使用它:

    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();
        }
    }
    
        6
  •  0
  •   Tim Cooper    12 年前

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