1. 继承构造函数
派生类如果要使用基类的成员函数,可以通过using声明来完成。
1 #include <iostream> 2 using namespace std; 3 4 class Base { 5 public: 6 void func(double f) { cout << "Base: " << f << endl; } 7 }; 8 9 class Derived: Base { 10 public: 11 using Base::func; 12 void func(int i) { cout << "Derived: " << i << endl; } 13 }; 14 15 int main() 16 { 17 Base b; 18 b.func(4.5); // Base: 4.5 19 Derived d; 20 d.func(4.5); // Base: 4.5 21 d.func(3); // Derived: 3 22 }
如上代码,使用了using声明,声明派生类Derived也使用基类版本的函数func。在C++11中,这个想法被扩展到构造函数上。
class A { public: A(int i) {} A(double d, int i) {} A(float f, int i, const char* c) {} }; class B: A { public: using A::A; // 继承构造函数 virtual void ExtraInterface() {} int d=3; // 默认值 }; int main() { //B b; // 编译报错,不会生成默认的构造函数 B() B b1(3); B b2(2.3, 3); B b3(2.3, 3, "abc"); }
这样使用using A::A 来声明把基类中的构造函数悉数继承到派生类B中。using继承有以下规则:
- 继承构造函数只会初始化基类中的成员变量
- 不能继承基类中的私有构造函数以及私有成员函数
- 派生类如果是从基类中虚继承的,那么不能在派生类中声明继承构造函数
- 一旦使用了继承构造函数,编译器就不会为派生类生成默认构造函数
2. 委派构造函数
如上例,我们在 Info(int) 和 Info(char)的初始化列表的位置调用了“基准版本”的构造函数Info() 。在初始化列表中调用“基准版本”的构造函数为委派构造函数,而被调用的“基准版本”则为目标构造函数。
所谓委派构造,就是指委派函数将构造的任务委派给了目标构造函数来完成的一种类构造方式。委派构造函数不能有初始化列表造成的,即构造函数不能同时“委派”和使用初始化列表,如果委派构造函数需要给成员变量赋初值,只能放在函数体中。
由于初始化列表的初始化方式总是先于构造函数完成的,这样上述初始化显得不够满意,可以改一下使得委派构造函数依然可以在初始化列表中初始化所有成员。
2.1 委派构造函数的应用
应用1:使用构造模板函数产生目标构造函数。
#include <list> #include <vector> #include <deque> using namespace std; class TDConstructed { template<class T> TDConstructed(T first, T last) : l(first, last) {} list<int> l; public: TDConstructed(vector<short> &v) : TDConstructed(v.begin(), v.end()) {} TDConstructed(deque<int> &d) : TDConstructed(d.begin(), d.end()) {} };
如上例子,定义了一个构造函数模板。而通过两个委派构造函数的委托,构造函数模板会被实例化。这样TDConstructed类可以很容易的接受多种容器对其进行初始化。委托构造使得构造函数的泛型编程也成为了一种可能。
应用2:异常处理,如果在委派构造函数中使用try的话,那么从目标构造函数产生的异常,都可以在委派构造函数中被捕捉到。
class DCExcept { public: DCExcept(double d) try : DCExcept(1, d) { cout << "Run the body." << endl; // 其他初始化 } catch (...) { cout << "caught exception." << endl; } private: DCExcept(int i, int d) { cout << "going to throw!" << endl; throw 0; } int type; double data; }; int main() { DCExcept a(1.2); }
上述代码中,目标构造函数DCExcept(int i, int d)抛出了一个异常,并在委派构造函数DCExcept(double d)进行捕捉。
3. 右值引用
一个左值表达式代表的是对象本身,而右值表达式代表的是对象的值;变量也是左值。可以这么理解:对于一个表达式,凡是对其取地址(&)操作可以成功的都是左值,否则就是右值。
3.1 C++拷贝构造函数问题
拷贝构造函数中为指针成员分配新的内存再进行内容拷贝的做法在C++编程中几乎被视为不可违背的。不过这样能带来一些问题。
3.2 移动构造函数
Test(Test &&t): m_p(t.m_p) { cout << "move constructor" << endl; t.m_p = nullptr; }
3.3 std::move
int main() { Test t1; // ...... Test t2(t1);// ...... }