懒惰/多级结构在C ++中
-
23-09-2019 - |
题
什么是好的现有类/设计为多级结构的图案/ C中的对象++这些初始化?
我有应在在该程序的流程不同的点进行初始化一些数据成员的一类,所以它们的初始化必须被延迟。例如,一个参数可以从网络中的文件和另一个进行读取。
目前我使用boost ::可选的数据成员的建设滞后,但它困扰着我,可选的比延迟构建。
语义不同我需要提醒升压的功能::绑定和lambda部分功能的应用程序,并使用这些库,我可能可以设计多级建筑 - 但我更喜欢使用现有的测试类。 (或者,也许有它,我不熟悉的另一种多级结构模式)。
解决方案
的关键问题是你是否应该区分不完全填充的对象的完全填充对象在类型级别即可。如果你决定不作区分,那么就使用boost::optional
或你正在做类似:这使得它可以轻松快速编码。 OTOH你不能让编译器强制执行特定的功能需要一个完全填充物的要求;需要执行运行时每次字段的检查。
参数组类型
如果你在类型级别区分不完全填充的对象完全填充对象,可以执行要求,即一个函数被传递一个完整的对象。为此,我建议建立每个相关类型XParams
相应类型X
。 XParams
具有boost::optional
构件和setter函数对于可以初始施工后设定每个参数。然后,你可以强制X
到只有一个(非副本)构造函数,它接受一个XParams
作为其唯一的参数,并检查每个必要的参数一直是XParams
对象内设置。 (不知道这种模式有一个名字 - 那样的人来编辑这个充满我们的)
“部分对象” 类型
这奇妙的作品,如果你真的没有来的做的有(也许不是像获得字段值回琐碎的东西等),它完全填充前的物体什么。如果你必须有时治疗不完全填充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&
的要求,所以这种方式服从的里氏替换原则。这意味着函数可以使用X&
的参数类型,以指示它需要一个完整X
对象,或XPartial&
以指示它能够处理部分填充对象 - 在这种情况下,或者一个XPartial
对象或全X
可以传递
我原本isComplete()
为protected
,却发现并没有工作,因为X
的拷贝构造函数和赋值操作符必须呼吁他们XPartial&
争论这个功能,他们没有足够的访问。上反射,它更有意义公开公开此功能。
其他提示
我必须在这里失去了一些东西 - 我做这种事情所有的时间。这是很常见的有一个是大和/或在任何情况下并不需要通过一类对象。所以他们创建动态!
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(也许内置),和大指针可能应该是可变的。当然A必须能够构建大,这可能在其他情况下,意味着缓存所包含的类的构造函数的参数。您还需要在复制/分配的政策决定 - 我可能会为这种类禁止双方
我不知道任何方式来处理这个具体问题。这是一个棘手的设计问题,一个很独特像C ++语言。另一个问题是,这个问题的答案是紧密联系在一起的您的个人(或企业)的编码风格。
我会使用指针这些部件,并且当它们需要被构造,在同一时间进行分配。您可以使用auto_ptr这些,并核对NULL,看看他们是否被初始化。 (我想指针被内置在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
}
请注意做事鲁莽,因为这可能会为你赢得你的同胞程序员的蔑视,除非你已经得到了使用该技术充分的理由。这也并不耽误的构造,只是让你再打电话吧。
使用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_;
}
我已经省略常量-访问语义
我所知道的最简单的方法是类似的迪特里希埃普提出的技术,但它可以让你真正延迟对象的建设,直到您所选择的时刻。
基本上:储备用malloc代替新对象(从而绕过构造函数),然后调用重载新的运营商时的真正想通过放置新构造对象
示例:
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 ++对象。作为迪特里希建议,你应该质疑是否你真的,真的必须延迟构造函数调用。在碱构造应该执行的最低限度,以获得您的对象进入可维修的状态,同时根据需要其他重载构造可以执行更多的工作。
我不知道是否有一个正式的模式这一点。在我见过的地方,我们把它称为“懒惰”,“需求”或“按需”。