7.2 默认函数的控制

类别:类作者

7.2.1 类与默认函数

在C++中声明自定义的类,编译器会默认帮助程序员生成一些他们未自定义的成员函数。这样的函数版本被称为“默认函数”。这包括了以下一些自定义类型的成员函数:

❑构造函数

❑拷贝构造函数

❑拷贝赋值函数(operator=)

❑移动构造函数

❑移动拷贝函数

❑析构函数

此外,C++编译器还会为以下这些自定义类型提供全局默认操作符函数:

❑operator ,

❑operator &

❑operator &&

❑operator *

❑operator ->

❑operator ->*

❑operator new

❑operator delete

在C++语言规则中,一旦程序员实现了这些函数的自定义版本,则编译器不会再为该类自动生成默认版本。有时这样的规则会被程序员忘记,最常见的是声明了带参数的构造版本,则必须声明不带参数的版本以完成无参的变量初始化。不过通过编译器的提示,这样的问题通常会得到更正。但更为严重的问题是,一旦声明了自定义版本的构造函数,则有可能导致我们定义的类型不再是POD的。我们可以看看代码清单7-7所示的例子。

代码清单7-7


include <type_traits>

include <iostream>

using namespace std;

class TwoCstor{

public:

//提供了带参数版本的构造函数,则必须自行提供

//不带参数版本,且TwoCstor不再是POD类型

TwoCstor(){};

TwoCstor(int i):data(i){}

private:

int data;

};

int main(){

cout<<is_pod<TwoCstor>::value<<endl;//0

}

//编译选项:g++ -std=c++11 7-2-1.cpp


代码清单7-7所示的例子中,程序员虽然提供了TwoCstor()构造函数,它与默认的构造函数接口和使用方式也完全一致,不过按照3.6节我们对“平凡的构造函数”的定义,该构造函数却不是平凡的,因此TwoCstor也就不再是POD的了。使用is_pod模板类查看TwoCstor,也会发现程序输出为0。对于形如TwoCstor这样只是想增加一些构造方式的简单类型而言,变为非POD类型带来一系列负面影响有时是程序员所不希望的(读者可以回顾一下3.6节,很多时候,这意味着编译器失去了优化这样简单的数据类型的可能)。因此客观上我们需要一些方式来使得这样的简单类型“恢复”POD的特质。

而在C++11中,标准是通过提供了新的机制来控制默认版本函数的生成来完成这个目标的。这个新机制重用了default关键字。程序员可以在默认函数定义或者声明时加上“=default”,从而显式地指示编译器生成该函数的默认版本。而如果指定产生默认版本后,程序员不再也不应该实现一份同名的函数。具体如代码清单7-8所示。

代码清单7-8


include <type_traits>

include <iostream>

using namespace std;

class TwoCstor{

public:

//提供了带参数版本的构造函数,再指示编译器

//提供默认版本,则本自定义类型依然是POD类型

TwoCstor()=default;

TwoCstor(int i):data(i){}

private:

int data;

};

int main(){

cout<<is_pod<TwoCstor>::value<<endl;//1

}

//编译选项:g++7-2-2.cpp-std=c++11


编译运行代码清单7-8,会得到结果1。TwoCstor还是一个POD的类型。

另一方面,程序员在一些情况下则希望能够限制一些默认函数的生成。最典型地,类的编写者有时需要禁止使用者使用拷贝构造函数,在C++98标准中,我们的做法是将拷贝构造函数声明为private的成员,并且不提供函数实现。这样一来,一旦有人试图(或者无意识)使用拷贝构造函数,编译器就会报错。

我们来看看代码清单7-9所示的例子。

代码清单7-9


include <type_traits>

include <iostream>

using namespace std;

class NoCopyCstor{

public:

NoCopyCstor()=default;

private:

//将拷贝构造函数声明为private成员并不提供实现

//可以有效阻止用户错用拷贝构造函数

NoCopyCstor(const NoCopyCstor&);

};

int main(){

NoCopyCstor a;

NoCopyCstor b(a);//无法通过编译

}

//编译选项:g++7-2-3.cpp-std=c++11


代码清单7-9中,NoCopyCstor b(a)试图调用private的拷贝构造函数,该句编译不会通过。不过这样的做法也会对友元类或函数使用造成麻烦。友元类很可能需要拷贝构造函数,而简单声明private的拷贝构造函数不实现的话,会导致编译的失败。为了避免这种情况,我们还必须提供拷贝构造函数的实现版本,并将其声明为private成员,才能达到需要的效果。

在C++11中,标准则给出了更为简单的方法,即在函数的定义或者声明加上“=delete”。“=delete”会指示编译器不生成函数的缺省版本。我们可以看代码清单7-10所示的例子。

代码清单7-10


include <type_traits>

include <iostream>

using namespace std;

class NoCopyCstor{

public:

NoCopyCstor()=default;

//使用“=delete”同样可以有效阻止用户

//错用拷贝构造函数

NoCopyCstor(const NoCopyCstor&)=delete;

};

int main(){

NoCopyCstor a;

NoCopyCstor b(a);//无法通过编译

}

//编译选项:g++7-2-4.cpp-std=c++11


代码清单7-10即是一个使用“=delete”删除拷贝构造函数的缺省版本的实例。值得注意的是,一旦缺省版本被删除了,重载该函数也是非法的。