构造函数

  • 在初识C++时,构造函数的这一称谓便出现了在了我们眼前。毕竟,C++作为一个面向对象的编程语言,这是实现其特性的一个关键要素。
  • 至于构造函数是什么,为什么关键当然是由于在实例化其对象时,所规定的一系列操作
1
2
3
class Test {
Test() {}
}

委托构造函数

  • 好了,浅浅的提了下基本知识。让我们迈入今天的正题好啦
  • 首先,提问。委托构造函数是什么?他因何而生???
  • 让我们想想这样的一个应用场景,当我们需要利用构造函数初始化不同的函数成员时,我们想到的第一反应是什么?
1
2
3
4
5
6
7
8
9
10
11
12
class X
{
public:
X() : a_(0), b_(0.) { CommonInit(); }
X(int a) : a_(a), b_(0.) { CommonInit(); }
X(double b) : a_(0), b_(b) { CommonInit(); }
X(int a, double b) : a_(a), b_(b) { CommonInit(); }
private:
void CommonInit() {}
int a_;
double b_;
};
  • 嘛,毫无疑问。功能被完美的实现了,不过有些令人头秃的是,这种写法显得冗余。
  • 当然,你可能会想到砍掉初始化列表这部分。将类成员的赋值放入CommonInit之中。没错,这也没有问题。但会不可避免的牺牲一定程度的效率。
  • 此外,还有很多种奇思妙想可以用于解决这种场景。但或多或少都会牺牲一定的性能或者显得冗余
  • 所以,当当当 委托构造函数的出现。完美的解决了这个问题
1
2
3
4
5
6
7
8
9
10
11
12
class X
{
public:
X() : X(0, 0.) {}
X(int a) : X(a, 0.) {}
X(double b) : X(0, b) {}
X(int a, double b) : a_(a), b_(b) { CommonInit(); }
private:
void CommonInit() {}
int a_;
double b_;
};
  • 可以看到,前三种函数的重载,都依赖于第四种重载的构造函数进行了重载。大幅提高的代码的可读性。
  • 语法毫无疑问地很简单。但有些注意事项需要注意。

注意事项

  • 每个构造函数都可以委托余下构造函数代理。即他们每个都可以既是委托构造函数,也是代理构造函数
    • 拷贝构造函数也可采用这种语法机制
  • 不要循环递归委托
    • 不会编译报错,会产生段错误
  • 如果一个函数为委托构造函数,那么则不能在基类中初始化成员
1
2
3
4
5
6
7
8
9
10
11
12
class X
{
public:
X() : a_(0), b_(0) { CommonInit(); }
X(int a) : X(), a_(a) {} // 编译错误,委托构造函数不能在初始化列表初始化成员变量
X(double b) : X(), b_(b) {}// 编译错误,委托构造函数不能在初始化列表初始化成员变量

private:
void CommonInit() {}
int a_;
double b_;
};
  • 委托构造函数的执行顺序是先执行代理构造函数的初始化列表,然后执行代理构造函数的主体,最后执行委托构造函数的主体.
  • 如果在代理构造函数执行完成后,委托构造函数主体抛出了异常,则自动调用该类型的析构函数。(C++标准)

委托模版构造函数

  • 主要是利用模版的泛型能力,避免撰写过多的代理构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <vector>
#include <list>
#include <deque>

class X {
template<class T> X(T first, T last) : l_(first, last) { }
std::list<int> l_;
public:
X(std::vector<short>&);
X(std::deque<int>&);
};
X::X(std::vector<short>& v) : X(v.begin(), v.end()) { }
X::X(std::deque<int>& v) : X(v.begin(), v.end()) { }

int main()
{
std::vector<short> a{ 1,2,3,4,5 };
std::deque<int> b{ 1,2,3,4,5 };
X x1(a);
X x2(b);
}