C++11学习笔记之2——类的构造

  1. 显示声明或显示禁止生成默认构造函数

在没有声明和定义无参数构造函数的情况下,编译器默认会生成一个无参数的默认构造函数,C++11中可以通过显示方法来声明或禁止生成这种构造函数,在下面还能看到相同的方法用于其它编译器可能“偷偷”生成函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass
{
public:
MyClass( ) = default; // default constructor
MyClass( int i );
};

class MyClass
{
public:
MyClass () = delete; // Disable default constructor
MyClass(int i); // Enable
};

可见如果不希望默认的无参数构造函数被编译器调用,可以禁用它,这样有助于减少错误对象的生成。

  1. 显示声明和显示禁止生成拷贝(复制)构造函数
1
2
3
4
5
6
7
class MyClass
{
public:
MyClass ();
MyClass(const MyClass& src) = default;
//MyClass(const MyClass& src) = delete; // 禁止拷贝赋值函数生成
};

禁止拷贝构造函数被调用比较有利用价值,这种方法通常可以用于禁止对象作为函数参数被调用,而不需要像以往把拷贝构造函数声明为private属性的作法了,也不需要提供拷贝构造函数的实现了。

1
2
3
4
5
class MyClass
{
private:
MyClass(const MyClass& src); // 以往的作法
}

当然如果声明了= delete 属性话,下面的构造方法也行不通了。

1
2
MyClass     myObj1;
MyClass myObj2(myObj1);
  1. 显示声明和显示禁止生成赋值运算函数

同样,default 和 delete的声明后缀也适用与赋值运算函数。虽然赋值运算函数算不上本文标题所说的”类的构造”范畴,但是如果对C++有所了解的话,那就很清楚它们之间是有多么的千丝万缕了。

这里先说明下赋值运算函数和拷贝构造函数的区别以及如何区分它们何时被调用:如果一个已经具有值的对象被改写,则这个动作的精确术语是“赋值运算”,因此赋值运算函数被调用。然而如果一个对象在创建时是由另一个对象作为副本来生成(例如对象作为函数参数的按值传递过程中,临时对象副本的生成),那就是拷贝构造,它构造出了一个全新的对象,更精确的术语是“复制构造”。这也就是为什么前者只能算得上是赋值的运算函数,而后者可以称为构造函数。

下面是具体声明的代码:

1
2
3
4
5
6
7
class MyClass
{
public:
MyClass ();
MyClass& operator= (const MyClass& rhs) = default;
//MyClass& operator= (const MyClass& rhs) = delete // 禁止生成赋值运算函数
};

禁止赋值运算函数的方法可以用于设计模式中某些场合中。例如可以用于对象封装的代理类操作共享数据内存的模式中。

  1. 初始化列表构造函数(Initializer-List Constructors)

这个名字看上去就很带劲儿、很神奇吧。C++11的确给我们带来了很多惊喜。

初始化列表构造函数是一个以initializer_list对象为参数的构造函数,后者是个模板类,需要包含头文件<intializer_list>,可以这么实现这个构造函数,大致代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <vector>
#include <initializer_list>

class MySequenceClass
{
public:
MySequenceClass(initializer_list<double> args)
{
for(auto citer : args)
m_VecPoints.push_back(*citer);
}

vector<double> m_VecPoints;
};

咋一看这个对象名**_list,很明显是个容器了,但是光从这个构造函数看不出其便捷和强大之处,看看下面如何构造这个类的对象就明白了:

1
2
3
4
5
MySequenceClass s1 = {1.0, 2.0, 3.0, 4.0, 5.0};
// same as : MySequenceClass s1{1.0, 2.0, 3.0, 4.0, 5.0};

for(auto point : s1.m_vecPoints)
std::cout << point << " " << std::endl;

怎么样,类的使用者完全不知道initializer_list的存在,对开发者来说数据的处理更加简单了,但是这种方法的效率还需要看具体C++11的实现,我觉得对于非基础数据类型的模板参数类型(例如大量的std::string)很可能会有效率上的急剧下降,因此还是推荐将引用或者smartptr作为模板参数类型来构造。如果是已存在的list(非常量),那用list&或其它的容器引用作为参数还来得更快。

  1. 类成员声明初始化

这项新特性应该很熟悉,大家都期望已久的声明初始化:

1
2
3
4
5
6
class MyClass
{
protected:
int m_nCount = 1;
std::string m_str = "test";
};

很简单明了,使用场景很普遍,就不多赘述了。

  1. 委托构造函数

委托这个词在很多高级语言中早就被引入了,而C++11的这个标准稍稍来迟了。

这里指的委托构造函数不是很复杂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyClass
{
public:
MyClass(int value);

MyClass(const string& strValue);

};

MyClass::MyClass(int value)
{

...

}

MyClass::MyClass(const string& strValue)
:MyClass(stringToint(strValue))

{

}

这个委托调用必须放在成员初始化列表(又称初始化器)“ : “中,当MyClass(stringToint(strValue))执行结束后才会返回string参数的构造函数。没有什么好多说的,不过有一点要注意,避免递归的委托调用:

1
2
3
4
5
MyClass::MyClass(int value)
: MyClass( intTostring(value) )// 递归调用
{
...
}

这样的递归调用是否在编译时期报错取决于编译器的实现了。

关于C++11新特性中类的构造就这样吧,后续有更新会再加入。

下一篇讲介绍C++11中关于类继承的新特性。