为什么使用初始化方法而不是构造函数?

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函数的另一个常见原因是希望避免异常,但这是一种非常古老的编程风格(并且它是一个好主意是否是它自己的完整论证).它与在构造函数中无法工作的事物无关,而与构造函数在某些事情失败时无法返回错误值的事实无关.所以,如果你的同事给你真正的理由,我怀疑这不是它.

  • 只是你的帖子的msdn参考:http://msdn.microsoft.com/en-us/library/ms182331%28VS.80%29.aspx (2认同)
  • 第一个确实是一个正当理由.它也被称为_two-phase-construction_.如果我需要这样的东西,我把它隐藏在一个物体的内部.我永远不会将这个暴露给我班级的用户. (2认同)

Mat*_* M. 30

是的,我可以想到几个,但一般来说这不是一个好主意.

大多数情况下,调用的原因是您只通过构造函数中的异常报告错误(这是真的),而使用经典方法可以返回错误代码.

但是,在设计合理的OO代码中,构造函数负责建立类不变量.通过允许默认构造函数,您允许一个空类,因此您必须修改不变量,以便接受"null"类和"有意义"类...并且每次使用该类必须首先确保该对象已经正确建造......这很糟糕.

所以现在,让我们揭穿"原因":

  • 我需要使用一个virtual方法:使用虚拟构造函数的习惯用法.
  • 还有很多工作要做:那么,无论如何都要完成工作,只需在构造函数中完成
  • 设置可能会失败:抛出异常
  • 我想保留部分初始化的对象:在构造函数中使用try/catch并在对象字段中设置错误原因,不要忘记assert在每个公共方法的开头确保对象在尝试使用之前可用它.
  • 我想重新初始化我的对象:从构造函数调用初始化方法,你将避免重复的代码,同时仍然有一个完全初始化的对象
  • 我想重新初始化我的对象(2):使用operator=(如果编译器生成的版本不符合您的需要,则使用copy和swap惯用法实现它).

如上所述,一般来说,不好主意.如果你真的想拥有"void"构造函数,请创建它们private并使用Builder方法.使用NRVO效率很高...... boost::optional<FancyObject>如果施工失败,您可以返回.

  • 我会选择另一种方式,"我希望有可能重新初始化我的对象:实现一个复制和交换`operator =`" (2认同)

Pét*_*rök 16

其他人列出了许多可能的原因(以及为什么大多数这些通常不是一个好主意的正确解释).让我发布一个(或多或少)有效使用init方法的例子,它实际上与时序有关.

在以前的项目中,我们有很多服务类和对象,每个对象都是层次结构的一部分,并且交叉以各种方式引用对方.所以通常情况下,用于创建ServiceA,你需要一个父服务对象,而这又需要一个服务容器,它已经依赖于一些特定的服务存在(可能包括ServiceA本身)在初始化时.其原因是,在初始化过程中,大部分的服务与其他服务自身注册为监听特定事件,和/或通知有关初始化成功的情况下,其他服务.如果其他服务没有在通知时间存在,登记并没有发生,因此该服务将无法接收重要邮件后,该应用程序的使用过程中.为了打破循环依赖的链条,我们不得不使用明确的初始化方法从构造函数中分离,从而有效地使全球服务初始化一个两阶段的过程.

所以,虽然一般不应该遵循这个习惯用法,但恕我直言,它有一些有效用途.但是,最好尽可能使用构造函数将其使用限制在最小值.在我们的例子中,这是一个遗留项目,我们还没有完全理解它的架构.至少init方法的用法仅限于服务类 - 通过构造函数初始化常规类.我相信有可能是重构该架构消除了服务的init方法的需求的一种方式,但至少我没有看到如何做到这一点(和坦率地说,我们有更迫切的问题当时我是为了对付该项目的一部分).

  • 如果您认为侦听器的注册是初始化的一部分,那么它只是初始化的两个阶段.在一个动态的世界观中,服务流行起来,听众的注册是系统生命的一部分. (2认同)

gsp*_*spr 7

我能想到的两个原因:

  • 说创建一个对象涉及许多繁琐的工作,这些工作可能会失败很多很多可怕而微妙的方法.如果你使用一个简短的构造函数来设置不会失败的rudamentary事物,然后让用户调用一个初始化方法来完成大工作,你至少可以确保你创建了一些对象,即使大工作失败了.也许该对象包含有关init失败的确切方式的信息,或者由于其他原因保持未成功初始化对象的重要性.
  • 有时您可能希望在创建对象很久之后重新初始化对象.这样,只需再次调用初始化方法而不破坏和重新创建对象.


Man*_*j R 5

此类初始化的另一个用途可以是对象池.基本上你只是从池中请求对象.池中已经创建了一些空白的N个对象.现在是调用者可以调用他/她喜欢设置成员的任何方法.一旦调用者完成了对象,它就会告诉池将其破坏.优点是在使用对象之前将保存内存,并且调用者可以使用它自己的合适的成员方法来初始化对象.对象可能有很多用途,但调用者可能不需要全部,也可能不需要初始化对象的所有成员.

通常会想到数据库连接.一个池可以有一堆连接对象,调用者可以填写用户名,密码等.


TRI*_*its 5

当编译器不支持异常时,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的构造函数.当它与硬件绑定时,初始化事件的顺序至关重要.