bas*_*ibe 47 c++ constructor initialization
我刚进入一家新公司,大部分代码库都使用初始化方法而不是构造函数.
struct MyFancyClass : theUberClass
{
MyFancyClass();
~MyFancyClass();
resultType initMyFancyClass(fancyArgument arg1, classyArgument arg2,
redundantArgument arg3=TODO);
// several fancy methods...
};
Run Code Online (Sandbox Code Playgroud)
他们告诉我这与时间有关.在构造之后必须完成一些事情,这些事情在构造函数中会失败.但是大多数构造函数都是空的,我没有看到任何不使用构造函数的原因.
所以我转向你,哦,C++的向导:为什么你会使用init方法而不是构造函数?
Ste*_*sop 66
因为他们说"计时",我想这是因为他们希望他们的init函数能够在对象上调用虚函数.这并不总是在构造函数中起作用,因为在基类的构造函数中,对象的派生类部分"尚不存在",特别是您无法访问派生类中定义的虚函数.相反,如果已定义,则调用函数的基类版本.如果没有定义,(暗示该函数是纯虚函数),则会得到未定义的行为.
init函数的另一个常见原因是希望避免异常,但这是一种非常古老的编程风格(并且它是一个好主意是否是它自己的完整论证).它与在构造函数中无法工作的事物无关,而与构造函数在某些事情失败时无法返回错误值的事实无关.所以,如果你的同事给你真正的理由,我怀疑这不是它.
Mat*_* M. 30
是的,我可以想到几个,但一般来说这不是一个好主意.
大多数情况下,调用的原因是您只通过构造函数中的异常报告错误(这是真的),而使用经典方法可以返回错误代码.
但是,在设计合理的OO代码中,构造函数负责建立类不变量.通过允许默认构造函数,您允许一个空类,因此您必须修改不变量,以便接受"null"类和"有意义"类...并且每次使用该类必须首先确保该对象已经正确建造......这很糟糕.
所以现在,让我们揭穿"原因":
virtual方法:使用虚拟构造函数的习惯用法.assert在每个公共方法的开头确保对象在尝试使用之前可用它.operator=(如果编译器生成的版本不符合您的需要,则使用copy和swap惯用法实现它).如上所述,一般来说,不好主意.如果你真的想拥有"void"构造函数,请创建它们private并使用Builder方法.使用NRVO效率很高...... boost::optional<FancyObject>如果施工失败,您可以返回.
Pét*_*rök 16
其他人列出了许多可能的原因(以及为什么大多数这些通常不是一个好主意的正确解释).让我发布一个(或多或少)有效使用init方法的例子,它实际上与时序有关.
在以前的项目中,我们有很多服务类和对象,每个对象都是层次结构的一部分,并且交叉以各种方式引用对方.所以通常情况下,用于创建ServiceA,你需要一个父服务对象,而这又需要一个服务容器,它已经依赖于一些特定的服务存在(可能包括ServiceA本身)在初始化时.其原因是,在初始化过程中,大部分的服务与其他服务自身注册为监听特定事件,和/或通知有关初始化成功的情况下,其他服务.如果其他服务没有在通知时间存在,登记并没有发生,因此该服务将无法接收重要邮件后,该应用程序的使用过程中.为了打破循环依赖的链条,我们不得不使用明确的初始化方法从构造函数中分离,从而有效地使全球服务初始化一个两阶段的过程.
所以,虽然一般不应该遵循这个习惯用法,但恕我直言,它有一些有效用途.但是,最好尽可能使用构造函数将其使用限制在最小值.在我们的例子中,这是一个遗留项目,我们还没有完全理解它的架构.至少init方法的用法仅限于服务类 - 通过构造函数初始化常规类.我相信有可能是重构该架构消除了服务的init方法的需求的一种方式,但至少我没有看到如何做到这一点(和坦率地说,我们有更迫切的问题当时我是为了对付该项目的一部分).
我能想到的两个原因:
此类初始化的另一个用途可以是对象池.基本上你只是从池中请求对象.池中已经创建了一些空白的N个对象.现在是调用者可以调用他/她喜欢设置成员的任何方法.一旦调用者完成了对象,它就会告诉池将其破坏.优点是在使用对象之前将保存内存,并且调用者可以使用它自己的合适的成员方法来初始化对象.对象可能有很多用途,但调用者可能不需要全部,也可能不需要初始化对象的所有成员.
通常会想到数据库连接.一个池可以有一堆连接对象,调用者可以填写用户名,密码等.
当编译器不支持异常时,init()函数很好,或者目标应用程序不能使用堆(通常使用堆来实现异常来创建和销毁它们).
当需要定义构造顺序时,init()例程也很有用.也就是说,如果全局分配对象,则不会定义调用构造函数的顺序.例如:
[file1.cpp]
some_class instance1; //global instance
[file2.cpp]
other_class must_construct_before_instance1; //global instance
Run Code Online (Sandbox Code Playgroud)
该标准不保证在instance1的构造函数之前调用must_construct_before_instance1的构造函数.当它与硬件绑定时,初始化事件的顺序至关重要.