优先选择删除函数,而非private未定义函数
如果你写了代码给其他程序员用,并且你想阻止它们调用某个特定函数的话,那你只需不要声明该函数即可。函数未经声明,不可调用。但是有时候c++会替你声明函数,而如果你要阻止这些客户调用这些函数。
这种情况仅仅发生在“特种成员函数”身上,即c++会在需要时自动生成的成员函数。
C++98为了阻止这些函数被使用,采取的做法是将其声明为private
,并且不去定义它们。
在C++中,对输入流和输出流进行复制是不可取的。为了让输入流和输出流成为不可复制的。
在C++98中的basic_ios
是像下面这样定义的
template<class charT, class traits=char_traits<charT>>
class basic_ios : public ios_base{
public:
...
private:
basic_ios(const basic_io&); //not defined
basic_ios& operator=(const basic_ios&); //not defined
};
通过将这些函数声明为private
,就阻止了客户去调用它们。而故意不去定义它们,就意味着如果一段代码仍然可以访问它们(如成员函数,或类的友元)并使用了它们,链接阶段就会由于缺少函数定义而告失败。
在C++11中,有更好的途径来达成效果上相同的结果:使用=delete
将拷贝构造函数和拷贝赋值运算符标识为删除函数。
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;
...
};
删除函数无法通过任何方法使用,所以即使成员和友元函数中的代码也会因试图复制basic_ios
而无法工作,这对于C++98是一种改进,因为在C++98中,后面这种不当使用直到链接阶段才能诊断出来。
习惯上,删除函数会被声明为public
,而非private
。当客户代码尝试使用某个成员函数时,C++会先校验可访问性,后校验删除状态,这么一来,当客户代码试图调用某个private
删除函数时,有些编译器只会抱怨该函数为private
,尽管函数的可访问性并不影响是否可用。
删除函数的一个重要优点在于,任何函数都能成为删除函数,但只有成员函数能声明为private
。
举例来说,假定有一个非成员函数,取用一个整数,并返回其是否是个幸运数:
bool isLucky(int number);
C++的C渊源决定了把可以凑合看作是数值的类型,都可以隐式转换到int
,但有些调用尽管可以编译,但语义上却没有意义。
if(isLucky('a')); //'a'是个幸运数么?
if(isLucky(true)); //true是个幸运数么?
if(isLucky(3.5)); //应该先截断为3再检查是否为幸运数
如果幸运数必须是整数,那就要阻止上面的调用通过编译。
有一个方法就是为我们想要滤掉的类型创建删除重载版本。
bool isLucky(int number); //原始版本
bool isLucky(char) = delete; //拒绝char类型
bool isLucky(bool) = delete; //拒绝bool类型
bool isLucky(double) = delete; //拒绝double和float类型
当float
类型面临转型到int
还是double
类型的选择时,C++会优先转型到double
类型。对于isLucky的调用如果传入float
,则会调用double
类型的重载版本,而不是int
的那个。只能说它会尝试调用double
类型形参的重载版本,但由于这个重载版本已经是个删除版本,所以编译就被阻止了。
尽管删除函数不可被使用,但它们还是程序的一部分,因此,它们在重载决议时还是会纳入考量。
if(isLucky('a')); //错误
if(isLucky(true)); //错误
if(isLucky(3.5f)); //错误
删除函数能够阻止那些不应该实现的模板。
举例,假设你需要一个和内建指针协作的模板
template<typename T>
void processPointer(T* ptr);
指针世界有两个异类,一个是void*
指针,无法对其执行解引用,自增,自减等操作。另一个是char*
,因为它们基本上表示的是C风格的字符串,而不是指向单个字符的指针。
在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;
如果是类内部的函数模板,并且你想通过private
声明来禁用某些具体实现,这是做不到的。因为你不可能给予成员函数模板的某个特化以不同主模板的访问层级。如果processPointer
是在Widget内部的一个成员函数模板,而你想禁止使用void*
指针来调用。
下面是c++98的做法。
class Widget{
public:
template<typename T>
void processPointer(T* ptr){}
private:
template<> //错误
void processPointer<void>(void*);
};
问题在于,模板特化必须在名字空间作用域而非类作用域内撰写。这个毛病在删除函数身上不会表现出来,因为它们根本不需要不同的访问层级,二来因为成员函数模板可以在类外被删除。
class Widget{
public:
template<typename T>
void processPointer(T* ptr){ ... }
};
template<>
void Widget::processPointer<void>(void*) = delete;
要点速记
- 优先选择删除函数,而非
private
未定义函数 - 任何函数都可以删除,包括非成员函数和模板具体化