条款11.优先选择删除函数,而不是private未定义函数

本文探讨了在C++编程中,如何使用删除函数代替private未定义函数来阻止函数调用。C++11引入的删除函数提供了一种更明确的方式来禁用拷贝构造函数和赋值运算符,确保编译时就能捕获错误。删除函数不仅适用于成员函数,也可用于非成员函数和模板具体实现,以避免不合适的类型转换或模板误用。此外,删除函数在重载解析中依然参与,增加了代码的清晰性和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

优先选择删除函数,而非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未定义函数
  • 任何函数都可以删除,包括非成员函数和模板具体化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值