翻译《有关编程、重构及其他的终极问题?》——19.如何合理的从一个构造函数中调用另外一个构造函数
2017-02-22 19:51
696 查看
翻译《有关编程、重构及其他的终极问题?》——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:统一初始化和代理构造函数
相关文章推荐
- 翻译《有关编程、重构及其他的终极问题?》——18.你在一个语言上积累的经验和知识不总是适用于另外一门语言
- 翻译《有关编程、重构及其他的终极问题?》——7.不要在循环中调用alloca()函数
- 翻译《有关编程、重构及其他的终极问题?》——6.当把一个指针明确的转换为整型时,请检查所有相关代码
- 翻译《有关编程、重构及其他的终极问题?》——14.一个好的编译器和代码风格还不够
- 翻译《有关编程、重构及其他的终极问题?》——24.override和final关键字应该成为你的新朋友
- 翻译《有关编程、重构及其他的终极问题?》——1. 别把编译器的事给做了
- 翻译《有关编程、重构及其他的终极问题?》——15.在你的代码中开始使用enum class吧
- 翻译《有关编程、重构及其他的终极问题?》——12.当使用拷贝黏贴,一定要特别注意最后一行
- 电子书《有关编程、重构及其他的终极问题?》的翻译
- 翻译《有关编程、重构及其他的终极问题?》——前言
- 翻译《有关编程、重构及其他的终极问题?》——22.不要使用#pragram warning(default-X)
- 翻译《有关编程、重构及其他的终极问题?》——2.比0大的并不意味着就只是1
- 翻译《有关编程、重构及其他的终极问题?》——3.复制一次,检查两次
- 翻译《有关编程、重构及其他的终极问题?》——25.不要再用this指针和nullptr比较了
- 翻译《有关编程、重构及其他的终极问题?》——16.在编程过程中“装逼”是不可接受的
- 翻译《有关编程、重构及其他的终极问题?》——21.正确的检查文件的结尾符(EOF)
- 翻译《有关编程、重构及其他的终极问题?》——8.记住:析构函数中的异常是危险的
- 翻译《有关编程、重构及其他的终极问题?》——10.避免使用多个小的#ifdef块
- 翻译《有关编程、重构及其他的终极问题?》——11.不要试图把尽量多的操作符放到一行代码里
- 翻译《有关编程、重构及其他的终极问题?》——27.狡猾的BSTR字符串