代码之家  ›  专栏  ›  技术社区  ›  Danra Bathsheba

C++中的惰性/多级构造

  •  7
  • Danra Bathsheba  · 技术社区  · 15 年前

    C++的多阶段构造/初始化的一个好的类/设计模式是什么?

    我有一个带有一些数据成员的类,这些数据成员应该在程序流的不同点进行初始化,因此它们的初始化必须延迟。例如,可以从文件中读取一个参数,也可以从网络中读取另一个参数。

    目前,我正在使用boost::optional来延迟构造数据成员,但是optional在语义上与delay-constructed不同,这让我很困扰。

    我需要的是boost::bind和lambda部分函数应用程序的特性,使用这些库,我可能可以设计多阶段的构造——但我更喜欢使用现有的、经过测试的类。(或者可能还有另一个我不熟悉的多阶段构建模式)。

    6 回复  |  直到 14 年前
        1
  •  4
  •   j_random_hacker    15 年前

    关键问题是是否应该区分完全填充的对象和未完全填充的对象 在类型级别 . 如果你决定不做区分,那就用 boost::optional 或者与您正在做的类似:这使得快速获得编码变得很容易。否则编译器无法强制要求特定函数需要完全填充的对象;每次都需要执行字段的运行时检查。

    参数组类型

    如果在类型级别上确实区分了完全填充的对象和未完全填充的对象,则可以强制要求向函数传递完整的对象。为此,我建议创建相应的类型 XParams 对于每种相关类型 X . X参数 boost::可选 初始构造后可以设置的每个参数的成员和setter函数。然后你可以强迫 X 如果只有一个(非复制)构造函数,则 X参数 作为它的唯一参数,并检查是否在 X参数 反对。(不确定这个模式是否有名字——有人想编辑这个来填充我们吗?)

    “部分对象”类型

    如果你不需要 在完全填充对象之前使用该对象的任何内容(可能不包括诸如获取字段值之类的琐碎内容)。如果你不得不有时治疗一个人口不全的 X 就像一个“满的” X ,您可以改为 X 从类型派生 XPartial ,包含所有的逻辑,加上 protected 用于执行前置条件测试的虚拟方法,用于测试是否填充了所有必需的字段。那么如果 X 确保它只能在完全填充的状态下构造,它可以用总是返回的琐碎检查重写那些受保护的方法 true :

    class XPartial {
        optional<string> name_;
    
    public:
        void setName(string x) { name_.reset(x); }  // Can add getters and/or ctors
        string makeGreeting(string title) {
            if (checkMakeGreeting_()) {             // Is it safe?
                return string("Hello, ") + title + " " + *name_;
            } else {
                throw domain_error("ZOINKS");       // Or similar
            }
        }
        bool isComplete() const { return checkMakeGreeting_(); }  // All tests here
    
    protected:
        virtual bool checkMakeGreeting_() const { return name_; }   // Populated?
    };
    
    class X : public XPartial {
        X();     // Forbid default-construction; or, you could supply a "full" ctor
    
    public:
        explicit X(XPartial const& x) : XPartial(x) {  // Avoid implicit conversion
            if (!x.isComplete()) throw domain_error("ZOINKS");
        }
    
        X& operator=(XPartial const& x) {
            if (!x.isComplete()) throw domain_error("ZOINKS");
            return static_cast<X&>(XPartial::operator=(x));
        }
    
    protected:
        virtual bool checkMakeGreeting_() { return true; }   // No checking needed!
    };
    

    虽然这里的继承看起来是“背对背”,但这样做意味着 X 可在任何地方安全供应 XPartial& 是被要求的,所以这个方法遵循 Liskov Substitution Principle . 这意味着函数可以使用 X& 表明它需要一个完整的 X 对象,或 第三方 以指示它可以处理部分填充的对象——在这种情况下 部分 对象或完整 X 可以通过。

    原来我有 isComplete() 作为 受保护的 ,但发现这不起作用 X 的复制运算符和赋值运算符必须在其 第三方 他们没有足够的权限。经过深思熟虑,公开公开这一功能更有意义。

        2
  •  1
  •   anon    15 年前

    我一定是错过了什么-我一直都在做这种事。在任何情况下,类都有很大和/或不需要的对象是很常见的。所以动态地创建它们!

    struct Big {
        char a[1000000];
    };
    
    class A {
      public: 
        A() : big(0) {}
       ~A() { delete big; }
    
       void f() {
          makebig();
          big->a[42] = 66;
       }
      private:
        Big * big;
        void makebig() {
          if ( ! big ) {
             big = new Big;
          }
        }
    };
    

    我不认为需要比这更新奇的东西,除了makebig()应该是const(可能是inline),而且大指针应该是可变的。当然,a必须能够构造big,这在其他情况下可能意味着缓存包含类的构造函数参数。你还需要决定一个复制/分配策略-我可能会禁止这两种类型的类。

        3
  •  0
  •   Dietrich Epp    15 年前

    我不知道有什么模式可以处理这个具体问题。这是一个棘手的设计问题,一个有点独特的语言,如C++。另一个问题是,这个问题的答案与您的个人(或公司)编码风格密切相关。

    我将为这些成员使用指针,当需要构造它们时,同时分配它们。您可以使用auto-ptr来处理这些文件,并检查是否初始化了这些文件。(我认为指针是C/C++/Java中内置的“可选”类型,还有其他语言,NULL不是有效指针)。

    风格方面的一个问题是,您可能依赖于构造函数来完成太多的工作。当我编写oo代码时,我让构造器做的工作足以使对象处于一致状态。例如,如果我有一个 Image 类,我想从文件中读取,我可以执行以下操作:

    image = new Image("unicorn.jpeg"); /* I'm not fond of this style */
    

    或者,我可以这样做:

    image = new Image(); /* I like this better */
    image->read("unicorn.jpeg");
    

    如果构造函数中有很多代码,就很难解释C++程序是如何工作的,尤其是当你问这个问题时,“如果构造函数失败了,会发生什么?”这是将代码移出构造函数的主要好处。

    我有更多的话要说,但我不知道你想怎么处理延误的建设。

    编辑:我记得有一种(有些反常的)方法可以在任意时间调用对象的构造函数。下面是一个例子:

    class Counter {
    public:
        Counter(int &cref) : c(cref) { }
        void incr(int x) { c += x; }
    private:
        int &c;
    };
    
    void dontTryThisAtHome() {
        int i = 0, j = 0;
        Counter c(i);       // Call constructor first time on c
        c.incr(5);          // now i = 5
        new(&c) Counter(j); // Call the constructor AGAIN on c
        c.incr(3);          // now j = 3
    }
    

    注意,如果你没有充分的理由使用这种技术,那么这样鲁莽的行为可能会让你受到其他程序员的蔑视。这也不会延迟构造函数,只是让您稍后再调用它。

        4
  •  0
  •   tony.ganchev    15 年前

    对于某些用例,使用boost.optional看起来是一个很好的解决方案。我没有玩太多,所以我不能评论太多。在处理此类功能时,我要记住的一件事是,我是否可以使用重载构造函数,而不是默认和复制构造函数。

    当我需要这样的功能时,我只需要使用一个指向必需字段类型的指针,如下所示:

    public:
      MyClass() : field_(0) { } // constructor, additional initializers and code omitted
      ~MyClass() {
        if (field_)
          delete field_; // free the constructed object only if initialized
      }
      ...
    private:
      ...
      field_type* field_;
    

    接下来,我将通过以下方法访问字段,而不是使用指针:

    private:
      ...
      field_type& field() {
        if (!field_)
          field_ = new field_type(...);
        return field_;
      }
    

    我省略了const访问语义

        5
  •  0
  •   Mahmoud Al-Qudsi    15 年前

    我所知道的最简单的方法与dietrich epp建议的技术相似,只是它允许你真正地延迟对象的构建,直到你选择的某个时刻。

    基本上:使用malloc而不是new保留对象(从而绕过构造函数),然后在 真正地 希望通过Placement New构造对象。

    例子:

    Object *x = (Object *) malloc(sizeof(Object));
    //Use the object member items here. Be careful: no constructors have been called!
    //This means you can assign values to ints, structs, etc... but nested objects can wreak havoc!
    
    //Now we want to call the constructor of the object
    new(x) Object(params);
    
    //However, you must remember to also manually call the destructor!
    x.~Object();
    free(x);
    
    //Note: if you're the malloc and new calls in your development stack 
    //store in the same heap, you can just call delete(x) instead of the 
    //destructor followed by free, but the above is the  correct way of 
    //doing it
    

    就我个人而言,我唯一使用过这种语法的时候,我必须使用C++ C++对象的一个定制的基于C的分配器。正如dietrich建议的那样,您应该质疑是否真的必须延迟构造函数调用。这个 基础 构造函数应该执行最小值以使对象进入可用状态,而其他重载构造函数可能根据需要执行更多的工作。

        6
  •  0
  •   plinth    15 年前

    我不知道这有没有正式的模式。在我见过的地方,我们称之为“懒惰”、“需求”或“随需应变”。