在构造函数中初始化

5

我看过很多代码,其中编码人员为类定义了一个init()函数,并在创建实例后的第一件事情就调用它。

在构造函数中完成所有初始化操作是否有害或有限制?


2
为什么这个标签既有Java又有C++?是故意的吗? - Greg Kopff
1
不是每次都会调用init进行初始化,而是在使用两个不同的构造函数时才会调用它,以避免代码复制。 - Anycorn
虚拟的init()可以用于多态初始化。 - vidit
6个回答

2
通常为了可维护性和减少代码大小,当多个构造函数调用相同的初始化代码时:
class stuff 
{
public:
    stuff(int val1) { init(); setVal = val1; }
    stuff()         { init(); setVal = 0; }

    void init()     { startZero = 0; }

protected:
    int setVal;
    int startZero;
};

2
相反的是:通常最好将所有初始化放在构造函数中。在C++中,“最佳”策略通常是将初始化放在初始化列表中,以便成员直接使用正确的值进行构造,而不是默认构造,然后再赋值。在Java中,您要避免使用函数(除非它是privatefinal),因为动态解析可能会使您进入尚未初始化的对象。
唯一使用init()函数的原因是因为您有许多具有显着共性的构造函数。(在C++的情况下,您仍然需要权衡默认构造,然后赋值与立即使用正确值进行构造之间的差异。)

1
在Java中,将构造函数保持简短并将初始化逻辑移入“init()”方法有很好的原因:
  • 构造函数不会被继承,所以任何子类都必须重新实现它们或提供使用“super”链接的存根
  • 您不应该在构造函数中调用可重写方法,因为您可能会发现对象处于不一致的状态下,即部分初始化

你们两个的论点似乎都反对使用init()方法。使用super可以清楚地表明你正在初始化基类,而init可能会被覆盖。而且,在派生类中定义init的事实非常强烈地反对在基类中使用它。 - James Kanze

0
如果您有多个相同类或不同类的对象需要使用彼此的指针进行初始化,以便至少存在一个指针依赖循环,那么您不能仅在构造函数中完成所有初始化。(当另一个对象尚未创建时,您如何构造具有指向另一个对象的指针/引用的第一个对象?)
其中一种情况是在事件模拟系统中,不同组件相互作用,因此每个组件都需要指向其他组件的指针。
由于无法在构造函数中完成所有初始化,因此至少有一些初始化必须在init函数中进行。这就引出了一个问题:哪些部分应该在init函数中完成?灵活的选择似乎是在init函数中完成所有指针初始化。然后,您可以按任意顺序构造对象,因为在构造给定对象时,您不必担心是否已经具有其需要知道的其他对象的必要指针。

0

这是一种设计选择。您希望保持构造函数尽可能简单,以便轻松阅读其操作。因此,您经常会看到调用其他方法或函数的构造函数,具体取决于语言。这使得程序员可以在不迷失代码的情况下阅读和跟踪逻辑。

就构造函数而言,您可能很快就会遇到希望触发大量事件的情况。良好的设计要求您将这些序列分解为更简单的方法,以使其更易于阅读并在未来更容易维护。

因此,没有任何伤害或限制,这是一种设计偏好。如果您需要在构造函数中完成所有初始化,则可以在那里完成。如果您只需要稍后完成它,则将其放入稍后调用的方法中。无论哪种方式,完全由您决定,并且没有硬性规定。


0

这是一个与对象构造函数中抛出的异常有关的设计模式。

在C++中,如果从对象构造函数中抛出异常,则语言运行时将认为该对象根本没有被构造。 因此,当对象超出范围时,对象析构函数不会被调用。

这意味着,如果你的构造函数中有类似以下代码:

int *p1 = new int;
int *p2 = new int;

在你的析构函数中加入以下代码:

delete p1;
delete p2;

如果构造函数中p2的初始化由于没有更多可用内存而失败,则new运算符会抛出bad_alloc异常。

此时,即使为p1正确分配了内存,您的对象也没有完全构建。

如果发生这种情况,析构函数将不会被调用,您将泄漏p1。

因此,您在构造函数中放置的代码越多,错误发生导致潜在内存泄漏的可能性就越大。

这就是该设计选择的主要原因,毕竟这并不太疯狂。

有关更多信息,请参见Herb Sutter的博客:C++中的构造函数异常


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接