翻译《有关编程、重构及其他的终极问题?》——19.如何合理的从一个构造函数中调用另外一个构造函数
标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
最后更新:2017年02月22日
19.如何合理的从一个构造函数中调用另外一个构造函数
这个问题是从LibreOffice项目中发现的。PVS-Studio诊断的错误描述为:V603 The object was created but it is not being used. If you wish to call constructor, ‘this->Guess::Guess(….)’ should be used(译者注:大意为有个创建了但没有被使用的对象,如果想调用构造函数,应该使用‘this->Guess::Guess(….)’这样)。
Guess::Guess()
{
language_str = DEFAULT_LANGUAGE;
country_str = DEFAULT_COUNTRY;
encoding_str = DEFAULT_ENCODING;
}
Guess::Guess(const char * guess_str)
{
Guess();
....
}
解释
好的程序员讨厌写模棱两可的代码,很棒。但在处理多个构造函数时,想尽量让代码简单和干净,就有些搬起石头砸自己的脚了。
你知道的,一个构造函数式不能被像普通函数那样调用的。如果我们写了“A::A(int x) { A(); }”这样的代码,运行时就会导致创建一个A类型的临时匿名对象,而不是调用那个没有参数的构造函数。
所以上面问题代码运行后,实际发生的事情是这样:一个Guess类型的临时匿名对象被创建出来后,马上就被销毁,导致其language_str等成员变量未初始化。
正确的代码
通常有三种方式在构造函数中避免模棱两可的代码。让我们看下把。
第一种方式是实现一个专门用来初始化的函数,然后在所有的构造函数中调用它。因为这个方式显而易见,我这里就不写示例代码了。
这种方式是一种不错的、可靠的、干净的并且安全的技术。然而一些差劲的程序员企图让他们的代码看起来更短。所以我不得不再说说另外两种方式。
这另外的两种方式是非常危险的,并且需要你充分理解它们是如何工作的,而且你要知道你必须面对的后果是什么。
第二种方式:
Guess::Guess(const char * guess_str)
{
new (this) Guess();
....
}
第三种方式:
Guess::Guess(const char * guess_str)
{
this->Guess();
....
}
这第二种以及第三种方式是相当危险的,因为类实例化了两次。这种代码会引起一些微妙的bug,所以坏处会多于好处。大家可以考虑一下类似这种构造函数在那些情况下是合适的,那些情况下不太合适。
这里有一个例子是合适的情况:
class SomeClass
{
int x, y;
public:
SomeClass() { new (this) SomeClass(0,0); }
SomeClass(int xx, int yy) : x(xx), y(yy) {}
};
上面这段代码是很安全的,因为其类中占有简单的数据类型,而且没有任何基类。所以两次的构造函数调用不会有任何危险。
然后,下面�有另外一段代码,当调用构造函数时会引起错误:
class Base
{
public:
char *ptr;
std::vector vect;
Base() { ptr = new char[1000]; }
~Base() { delete [] ptr; }
};
class Derived : Base
{
Derived(Foo foo) { }
Derived(Bar bar) {
new (this) Derived(bar.foo);
}
Derived(Bar bar, int) {
this->Derived(bar.foo);
}
}
我们使用了语句“new (this)Derived(bar.foo);”或者“this->Derived(bar.foo)”来调用构造函数。
基类的对象已经被创建了,而且其成员变量也被初始化了。再次调用构造函数会导致初始化两次。结果指针ptr被再次新分配的内存地址给替换了,导致内存泄露。至于对std::vector类型的两次初始化,其结果就更难预测了。只有一件事情是肯定的:像这样的代码不应被允许。
难道我们要处理这么多头疼的事情才能解决这个问题吗?如果你不使用C++11的特性,那么就是用第一种方式吧(创建一个专门的初始化函数)。其实,一个明确的调用构造函数的需求是很少见的。
建议
现在我们还有最后一个特性要介绍,用来帮助我们解决调用构造函数的问题!
C++11允许我们的构造函数调用其他构造函数(被称之为代理)。这种方式可以让构造函数用最少的代码使用其他构造函数。
比如:
Guess::Guess(const char * guess_str) : Guess()
{
....
}
要像学习更多有关代理构造函数的知识,请看如下链接:
1. Wikipedia,C++11:对象构造函数的改进
2. C++11 FAQ:代理构造函数
3. MSDN:统一初始化和代理构造函数