【问题标题】:creating objects dynamically in C++在 C++ 中动态创建对象
【发布时间】:2008-10-28 13:43:35
【问题描述】:

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

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

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

【问题讨论】:

  • 能否请您明确说明您要问什么而不是提及另一个问题?谢谢。

标签: c++ serialization


【解决方案1】:

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

这通常是我所做的。它假定有一些键可以为您提供类型(int 标记、字符串等)。我创建了一个类,它有一个键到工厂函数的映射,以及一个注册函数,它接收一个键和工厂函数并将其添加到映射中。还有一个 create 函数,它接受一个键,在 map 中查找它,调用工厂函数,并返回创建的对象。例如,使用一个 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
}

【讨论】:

  • 对于某些设计,Factory 接口及其实现,加上 FactoryRegistry 比函数更好。但本质上是一样的。不错。
【解决方案2】:

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

【讨论】:

  • 我知道我所说的被称为工厂模式......我应该提到这一点。不过,我正在谈论如何实现该模式的核心。我之前不知道 Builder,但它似乎只是零碎的工厂模式。
  • 好的,我不确定您是否知道模式名称。维基百科链接以及这里的其他几个答案都有很好的实现选择。我同意 Kenny 的观点:如果你还没有拿起 Goff 的书,那么值得投资。
【解决方案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;
}

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      除非我遗漏了什么,否则你不需要 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();
          }
      }
      

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-12-16
          • 1970-01-01
          • 2019-10-21
          • 2018-05-25
          • 1970-01-01
          • 2014-06-03
          • 2017-06-11
          相关资源
          最近更新 更多