Effective Modern C++ 条款11 用deleted functions代替private undefined的做法

用deleted functions代替private undefined的做法

如果你把代码提供给别的开发者,那么你会想要防止他们调用一些特殊的函数,总的来说你可以不声明它们。不对外声明函数,就无法调用函数啦,这太容易了。但是有时候C++会自动声明函数,而如果你想要阻止用户调用这些C++自动声明的函数,那么就不是很容易啦。

这种情况只会在“特殊的成员函数”中出现,例如,一些类需要的成员函数C++会自动生成。条款17会详细讨论它们,不过现在我们只讨论拷贝构造函数(copy constructor)和赋值构造函数(copy assignment operator)。这章节(条款7~17)致力于把C++98的通用实践代替为C++11更好的实践,然后在C++98中,如果你像抑制一个成员函数,那么这成员函数几乎总是拷贝构造函数,或者赋值构造函数,再或者两个都抑制。

C++98中防止使用这些函数的方法是把这些函数声明在private内,然后不去定义它们。例如,接近C++标准库的io类(iostreams)基类的是模板类basic_ios,所有的输入流和输出流都会直接或间接继承它。拷贝输入输出流是不需要的,因为这样操作后不清楚会发生什么。例如,一个输入流对象(istream),表现为一个输入值的流,已经读入了一些值,可能之后还会读入剩下的值,那么如果这个对象被拷贝了,是要拷贝已读的值还是连同以后也会读入的值一起拷贝?解决这个问题的最简单办法是定义它不存在,就是禁止拷贝流对象。

为了让输入流和输出流无法拷贝,basic_ios在C++98中是这样被描述(包括注释):

template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
  ...
private:
  basic_ios(const basic_ios&);    // not defined
  basic_ios& operator=(const basic_ios&);  // not defined
};

把函数声明为private来防止用户调用它们。故意不去定义它们意味着如果仍有代码有权使用它们(其他成员函数或者友元类),会因找不到定义而链接失败。

在C++11中,有个刚好的办法来实现这个效果:使用“ = delete”把拷贝构造函数和赋值构造函数标记为deleted functions(被删除的函数)。这是用C++11修饰的basic_ios

template <class charT, class traits = char_traits<char T> >
class basic_ios : public ios_base {
public:
  ...
  basic_ios(const basic_ios&) = delete;
  basic_ios& operator=(const basic_ios&) = delete;
  ...
};

看起来,被删除函数和把函数声明为private之间的差别只是删除函数时髦一点而已,不过它们之间的差别比你想象中要大。删除的函数在任何方式上都无法使用,所以在成员或者友元中尝试拷贝basic_ios对象会无法通过编译。这是C++98的一个改进,这样就不用到链接期间才诊断出不合法使用。

按照惯例,被删除函数(deleted functions)声明为public,而不是private。当用户代码尝试调用一个成员函数时,C++会在检查它的删除状态位之前检查它的可获取性(accessibility,即是否为public?)。当用户尝试调用一个声明为private的删除函数时,一些编译器会抱怨这些删除的函数被声明为private,虽然这些函数的可获取性不会影响到函数能否被使用。值得牢牢记在心中的是,当你修改旧程序时遇到private-and-not-defined函数,用被删除函数(deleted function)代替它们。


删除函数还有一个重大优势是它可以用于任何函数,相比于private只能用在成员函数中。例如,我们有个非成员函数接收一个整数,然后返回是否为幸运数字:
bool isLucky(int number);

C++继承了C的隐式类型转换,在这里一些可数值化类型可能会被隐式转换成int,但这样函数调用就失去原本的意义了:

if (isLucky('a')) ...   // ‘a'是一个幸运的数字吗?

if (isLucky(true)) ...   // “true”是一个幸运的数字吗?

if (isLucky(3.5)) ...    //  在检查是否幸运之前要把3.5截成3吗?

如果幸运数字必须是整数,那么我们要在编译时阻止上面的函数调用。一个方法是,为想要过滤的类型创建重载函数,然后删除它们:

bool isLucky(int number);

bool isLucky(char) = delete;  // 拒绝char参数

bool isLucky(bool) = delete;  // 拒绝bool参数

bool isLucky(double) = delete;   // 拒绝double和float参数float在选择doubleint时,会优先选择转换为double,故float参数会选择这个重载函数)

尽管被删除的函数无法使用,但它们仍然是你程序的一部分,因此,重载决策时还是会考虑它们。这就是为什么声明了上面的删除函数后,不想要的isLucky调用会被拒绝:

if (isLucky('a'))...    // 报错,调用了被删除的函数

if (isLucky(true))...   // 报错

if (isLucky(3.5f))...      // 报错

被删除函数还能表演的另外一种把戏(private成员函数不能)是防止模板使用不想要的类型实例化。例如,你需要一个接收内置指针类型的模板(第4章建议你使用智能指针):
template <typename T> void processPointer(T *ptr);

在指针的世界里有两种特殊的指针类型。一种是void *,因为无法解引用它,无法递增或递减。一种是char *,因为它通常表示C-style字符串,而不是指向单独字节的指针。这两种特殊类型的指针经常需要特别的处理,然后在processPointer这个模板中,我们假定针对这两种类型的合适处理是禁止调用。也就是说,不应该用void *char *来调用processPointer

这很容易实现,就是删除它们的实例化:

template<>
void processPointer<void>(void *) = delete;

template<>
void processPointer<char>(char *) = delete;

现在用void *char *调用processPointer是无效的,但用const void *const char *调用是有效的,所以这些实例化通常也有删除:

template<>
void processPointer<const void>(const void *) = delete;

template<>
void processPointer<const char>(const char *) = delete;

不过如果你真的想做彻底,你还需删除const volatile void *const volatile char *的实例化,然后还有其他的字符类型:std::wchar_tstd::char16_tstd::char32_t,删除它们的指针实例。

有趣的是,如果你在一个类内有个函数模板,然后你想要通过private声明禁止一些实例,那么你是不可行的,因为成员模板的特例化与主模板的访问权限不相同是不可能。例如,processPointerWidget类内的模板成员,你想要使void *指针的调用无效:

class Widget {
public:
  template <typename T>
  void processPointer(T *ptr)
  {...}
private:
  template<>          // 错误
  void processPointer<void>(void *);
};

这里的问题在于模板的实例化必须写在命名空间,而不是类内空间。这个问题不会出现在deleted functions,因为它们不需要不同的访问权限。它们可以在类外被删除(因此在命名空间中):

class Widget {
public:
  template <typename T>
  void processPointer(T *ptr)
  {...}
 ...
};

template<>    // 仍然是public, 但被删除了
void Widget::processPointer<void>(void *) = delete;

事实上,在C++98中声明private函数然后不定义它,这种实践是一种尝试,试图达到C++11中删除函数这样的效果。作为竞争,C++98的方法不如真正删除的好,它既不能在类外工作,在类内又不是总是可行,还要到链接期间才发挥作用。所以,坚持使用deleted functions吧。

总结

需要记住的2点:

  • deleted functions代替private undefine的做法。
  • 任何函数都有可能被删除,包括非成员函数和模板的实例化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值