Item 11: Prefer deleted functions to private undefined ones

如果你为其他的开发者提供代码,但是又不想让他们使用某个特定的函数,那么不声明这个函数就好了。没有声明,就没办法调用嘛,简单的很。但对于C++为我们自动生成那些函数,想要阻止客户无法调用这些函数,就没那么简单了。
上述情况发生在一些“特别的成员函数”上。在item17中会详细讨论这些函数,但现在,我们仅以拷贝构造函数和拷贝赋值运算符为例。在C++98中,阻止这些函数被调用的办法就是将这些函数声明为private。以STL中的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
};

特意不提供这些函数的定义,是因为存在一些可以访问他们的代码(比如成员函数,或者友元类),如果有对他们的调用,那么会因为没有函数的定义而导致链接失败。
在C++11中,存在从根本上解决该问题的办法:利用“= delete”将copy constructor和copy assignment operator标记为删除的函数。同样,在C++11中,basic_ios实现如下:

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

把这些函数“删除掉”和把它们声明为private的不同之处看起来除了更时尚一点就没别的了,但是这里有一些实质上的优点是你没想到的。deleted的函数不能以任何方式调用,所以就算在成员函数和友元函数中,它们如果尝试拷贝basic_ios对象,它们也会失败。比起C++98,这样的错误使用在链接前是无法被诊断出来的,这就是是一个改进。
按照约定,删除的函数要被声明为public,而不是private。这是有原因的。当调用成员函数时,C++会先检查它访问权限,再看它是否被delete。那么,当调用一个private的delete函数,有些编译器只会提示这个函数访问权限方面的信息,即使它也是一个无法被调用的函数。所以,记住这一点,将delete的函数声明为public,会得到更多的编译提示信息。
delete的一个重要特点是,任何函数都可以被声明为delete,而只有成员函数才可以被声明private。例如,我们有一个普通的函数,isLucky,参数是interger,返回一个bool型:

bool isLucky(int number);

C++是由C发展来的,这就意味着可以凑合着看作数值的类型,都可以隐式的转换到int,但这些可以通过编译的调用,根本就没设么意义:

if (isLucky('a'))// is 'a' a lucky number?
if (isLucky(true))// is "true"?
if (isLucky(3.5))// should we truncate to 3
                            // before checking for luckiness?

如果lucky numbers必须是整形,我们想要阻止类似上面的调用。其中一种实现方法是重载那些不想要的调用函数,并声明为delete:

bool isLucky(int number);        // original function
bool isLucky(char) = delete;     // reject chars
bool isLucky(bool) = delete;     // reject bools
bool isLucky(double) = delete;   // reject double and floats

注:上面的“// reject double and floats”可能会让你有些纳闷,但你应该知道float到int的转换和float到double的转换,C++更倾向于将float转换到double。通过传float调用isLucky,都会调用到isLucky(double),而不是isLucky(int)。
虽然删除的函数无法被使用,但它们也是程序的一部分。既然如此,在重载解析时,它们也会被考虑进去。这就是为什么只要使用上面这样的deleted函数声明式,令人讨厌的调用就被拒绝了:

if (isLucky('a'))// error! call to deleted function
if (isLucky(true))// error!
if (isLucky(3.5f))// error!

deleted函数还有另外一个使用技巧(private 成员函数做不到),那就是阻止不需要的template实例。举个例子,假设你需要一个使用built-in指针的template(第四章的建议是,比起raw指针,优先使用智能指针):

template<typename T>
void processPointer(T* ptr);

在指针的世界中,有两个特殊的情况。一个是void*,因为没有办法对它们解引用,也没有办法对他们做加减运算等。另一个是char*,因为它常被用来代表指向C风格字符串的指针,而不是指向单个字符的指针。这些特殊的情况常常需要特别处理。现在,在processPointer template中,让我们假设我们需要做的特殊处理是拒绝这些类型的调用。也就是不能使用void* 或 char*指针来调用processPointer。
这很容易执行,只要把他们的实例删除(delete)掉:

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

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

现在,我们用void或者char调用processPointer是无效的,const void和const char可能也需要是无效的,因此,这些实例也需要被删除(delete):

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

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

并且你真想做的很彻底的话,你还需要删除(delete)掉const volatile void和const volatile char重载,然后你需要再为其他标准字符类型(std::wchar_t, std::char16_t以及std::char32_t)做这样的工作。
有意思的是,如果在class内有一个函数模板,然后你想通过把特定的实例声明为private(啊啦,典型的C++98的用法)来使它们无效,这是无法实现的,因为你无法把一个成员函数模板特化为不同的访问等级(和主template的访问等级不同)。举个例子,如果processPointer是一个内嵌于Widget的成员函数template,然后你想让void*指针的调用失效,尽管无法通过编译,C++98的方法看起来像是这样:

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

private:
	template<>
	void processPointer<void>(void*);// error
};

问题在于template特化必须被写在命名空间的作用域中,而不是类的作用域中。这个问题不会影响deleted函数,因为他们不需要不同的访问等级。他们能在class外面被删除(因此处在命名空间的作用域中):

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

...
};

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

事实上,C++98中,声明函数为private并不定义它们就是在尝试实现C++11的deleted函数所实现的东西。作为一个模仿,C++98的方法没有做到和实物(C++11的deleted函数)一模一样。它在class外面无法工作,它在class内不总是起作用,就算起作用,在链接前也可能不起作用。所以坚持使用deleted函数吧!!!
To Remember

  • 优先选用delete,而不是private;
  • 任何函数都能被删除(deleted),包括非成员函数和template实例化函数;

你可能感兴趣的:(Effective,Modern,C++,c++)