原文 from www.learncpp.com/cpp-tutorial/88-constructors-part-ii/
私有构造函数
偶尔,我们不想让用户在class外使用特殊的构造函数。为了实现这个想法,我们可以将构造函数设定为私有的。
1: class Book
2: {
3: private:
4: int m_nPages;
5:
6: // This constructor can only be used by Book's members
7: Book() // private default constructor
8: {
9: m_nPages = 0;
10: }
11:
12: public:
13: // This constructor can be used by anybody
14: Book(int nPages) // public non-default constructor
15: {
16: m_nPages = nPages;
17: }
18: };
19:
20: int main()
21: {
22: Book cMyBook; // fails because default constructor Book() is private
23: Book cMyOtherBook(242); // okay because Book(int) is public
24:
25: return 0;
26: }
public的构造函数具有的一个问题是它们没有提供任何控制一个特殊的类能够被创建多少次的方法。如果一个public构造函数存在,就能够人用户的愿望实例化足够多的类对象。通常限制用户只能够实例化一个特殊的类是有用的。有很多方式实现单例(singletons),最常用的是使用私有或保护的构造函数。
Constructor chaining and initialization issues
当你实例化一个新的对象的时候,对象构造函数被编译器隐式调用。有两个状况可能是新手经常犯错的地方:
1)有时候,一个类具有一个能够做另一个构造函数相同工作的构造函数,并增加额外的工作。处理器让一个构造函数调用另一个构造函数,叫做constructor chaining。如果你尝试这样做,它能够通过编译,但是它将不能够正确的工作,然后你就得花费大量的时间去找出原因,甚至使用debugger。但是,构造函数允许调用非构造函数。通常要小心,使用已经初始化的变量。
(译者加:这里C++中为什么不能使用constructor chaining呢,可以跟踪一下下面这段代码答案就出来了:
1: #include <iostream>
2: using namespace std;
3:
4: class Foo
5: {
6: int m_nValue;
7: public:
8: Foo() { m_nValue = 1; }
9:
10: // 这里没用到a,因为只是一个示例
11: Foo(int a) { Foo(); }
12:
13: ~Foo() { cout << "destructed" << endl; }
14:
15: int getValue() { return m_nValue; }
16: };
17:
18: int main()
19: {
20: Foo cTmp(1);
21: cout << cTmp.getValue1();
22: }
)
尽管你能将一个构造函数中的代码复制到另一个构造函数中,重复性的代码会使得你的类变得臃肿。最好的解决方法是创建一个非构造函数实现公共的初始化。
1: class Foo
2: {
3: public:
4: Foo()
5: {
6: // code to do A
7: }
8:
9: Foo(int nValue)
10: {
11: // code to do A
12: // code to do B
13: }
14: };
变为
1: class Foo
2: {
3: public:
4: Foo()
5: {
6: DoA();
7: }
8:
9: Foo(int nValue)
10: {
11: DoA();
12: // code to do B
13: }
14:
15: void DoA()
16: {
17: // code to do A
18: }
19: };
2)也许你想要一个成员函数实现将成员变量设定为默认值。因为你可能已经通过构造函数实现了,你也许会在成员函数中调用构造函数。但是这在C++中是不合法的。
类似1)中实现的方式
1: class Foo
2: {
3: public:
4: Foo()
5: {
6: Init();
7: }
8:
9: Foo(int nValue)
10: {
11: Init();
12: // do something with nValue
13: }
14:
15: void Init()
16: {
17: // code to init Foo
18: }
19: };
如果Init中有动态分配内存的情况,需要更加小心的对待了。在执行相应的操作之前,应该进行一定的检测。避免错误的发生。