C++11 defaulted和deleted函数从入门到精通

引言
在C++编程的世界里,C++11标准的出现带来了许多令人瞩目的新特性,其中defaulted和deleted函数尤为引人注目。它们为程序员提供了更精细的控制手段,能够显著提升代码的质量和可维护性。本文将带领你从基本概念出发,逐步深入了解这两个特性,通过丰富的代码示例和详细的解释,帮助你全面掌握defaulted和deleted函数的使用方法。
基本概念
Defaulted函数
在C++中,类有四类特殊成员函数,分别是默认构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符。这些特殊成员函数负责创建、初始化、销毁或者拷贝类的对象。当程序员没有显式地为一个类定义某个特殊成员函数,而又需要用到该特殊成员函数时,编译器会隐式地为这个类生成一个默认的特殊成员函数。
然而,当程序员显式地定义了某个特殊成员函数时,编译器将不再自动生成默认的构造函数。如果还想使用默认构造函数,就需要手动编写,这不仅增加了程序员的工作量,而且手动编写的代码执行效率往往比编译器自动生成的默认特殊成员函数低。
为了解决这些问题,C++11标准引入了defaulted函数。程序员只需在函数声明后加上“=default;”,就可将该函数声明为defaulted函数,编译器将为显式声明的defaulted函数自动生成函数体。
以下是一个简单的示例:
#include <iostream>
class MyClass {
public:
// 默认构造函数
MyClass() = default;
// 拷贝构造函数
MyClass(const MyClass&) = default;
// 赋值运算符
MyClass& operator=(const MyClass&) = default;
// 析构函数
~MyClass() = default;
};
int main() {
MyClass obj; // 调用默认构造函数
MyClass obj2 = obj; // 调用默认拷贝构造函数
return 0;
}
在这个示例中,我们使用“=default;”显式地声明了默认构造函数、拷贝构造函数、赋值运算符和析构函数,编译器会为这些函数自动生成默认的实现。
Deleted函数
在某些情况下,我们可能不希望类的某些函数被调用,例如禁止类的拷贝操作,或者避免某些不恰当的函数重载被调用。为了满足这些需求,C++11标准引入了deleted函数。
程序员只需在函数声明后加上“=delete;”,就可将该函数禁用。被声明为deleted的函数不能以任何方式被调用,即使在成员函数或者友元函数中调用也会在编译时失败。
以下是一个禁止类拷贝操作的示例:
#include <iostream>
class NonCopyable {
public:
// 默认构造函数
NonCopyable() = default;
// 拷贝构造函数,禁止拷贝
NonCopyable(const NonCopyable&) = delete;
// 拷贝赋值运算符,禁止拷贝赋值
NonCopyable& operator=(const NonCopyable&) = delete;
// 析构函数
~NonCopyable() = default;
int data;
};
int main() {
NonCopyable obj1;
obj1.data = 10;
// 以下代码会导致编译错误,因为拷贝构造函数被删除
// NonCopyable obj2(obj1);
// 以下代码会导致编译错误,因为拷贝赋值运算符被删除
// NonCopyable obj3;
// obj3 = obj1;
return 0;
}
在这个示例中,我们使用“=delete;”显式地删除了拷贝构造函数和拷贝赋值运算符,这样就禁止了NonCopyable类的对象被拷贝。
入门使用
Defaulted函数的使用规则
- 仅适用于类的特殊成员函数:defaulted函数特性仅适用于类的特殊成员函数,如默认构造函数、拷贝构造函数、析构函数等。非特殊成员函数不能使用“=default;”声明。
- 无默认参数:该特殊成员函数不能有默认参数。例如,以下代码是错误的:
class X {
public:
// 错误,func不是特殊成员函数
int func() = default;
// 错误,构造函数X(int, int)不是特殊成员函数
X(int, int) = default;
// 错误,构造函数X(int=1)含有默认参数
X(int = 1) = default;
};
- 可内联和外联定义:defaulted函数既可以在类体里(inline)定义,也可以在类体外(out-of-line)定义。例如:
class X {
public:
X() = default; // Inline defaulted默认构造函数
X(const X&);
X& operator=(const X&);
~X() = default; // Inline defaulted析构函数
};
X::X(const X&) = default; // Out-of-line defaulted拷贝构造函数
X& X::operator=(const X&) = default; // Out-of-line defaulted拷贝赋值操作符
Deleted函数的使用规则
- 函数首次声明时删除:必须在函数第一次声明的时候将其声明为deleted函数,否则编译器会报错。即对于类的成员函数而言,deleted函数必须在类体里(inline)定义,而不能在类体外(out-of-line)定义。例如:
class X {
public:
X(const X&);
};
// 错误,deleted函数必须在函数第一次声明处声明
X::X(const X&) = delete;
- 无函数类型限制:不同于defaulted函数,deleted函数没有限制为特殊成员函数才能使用。非类的成员函数,即普通函数也可以被声明为deleted函数。例如:
// 禁用add函数
int add(int, int) = delete;
int main() {
int a, b;
// 错误,函数add(int, int)被禁用
add(a, b);
return 0;
}
高级应用
在类设计中的应用
实现不可拷贝类
在某些情况下,我们希望某个类的对象不能被拷贝,这时可以使用deleted函数来禁止拷贝构造函数和拷贝赋值运算符。例如:
class NonCopyableClass {
public:
NonCopyableClass() = default;
NonCopyableClass(const NonCopyableClass&) = delete;
NonCopyableClass& operator=(const NonCopyableClass&) = delete;
~NonCopyableClass() = default;
};
这样,NonCopyableClass类的对象就不能被拷贝,从而避免了不必要的资源复制和潜在的错误。
控制对象的创建方式
我们可以使用deleted函数来控制对象的创建方式,例如禁止在堆上创建对象:
class OnlyStackObject {
public:
OnlyStackObject() = default;
~OnlyStackObject() = default;
void* operator new(size_t) = delete;
void* operator new[](size_t) = delete;
};
在这个例子中,由于new和new[]运算符被删除,OnlyStackObject类的对象只能在栈上创建,不能在堆上动态分配。
在模板编程中的应用
禁用特定类型的模板实例化
在模板编程中,我们可能希望某些类型禁止使用某个模板,这时可以使用deleted函数来实现。例如:
template<typename T>
void process(T value) {
// 处理逻辑
}
// 禁止使用int类型的模板实例化
template<>
void process<int>(int value) = delete;
int main() {
// 错误,调用了被删除的函数
// process(10);
process(3.14); // 正确
return 0;
}
在这个例子中,我们使用deleted函数禁止了process模板函数对int类型的实例化。
避免隐式类型转换
在模板函数中,可能会存在隐式类型转换导致的问题,我们可以使用deleted函数来避免这种情况。例如:
template<typename T>
bool isLucky(T number) {
// 假设判断幸运数的逻辑
return true;
}
// 拒绝char、bool和double类型的调用
bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete;
int main() {
// 错误,调用了被删除的函数
// isLucky('a');
// isLucky(true);
// isLucky(3.5);
isLucky(10); // 正确
return 0;
}
在这个例子中,我们使用deleted函数拒绝了isLucky函数对char、bool和double类型的调用,避免了不必要的隐式类型转换。
使用注意事项和常见错误
Defaulted函数的注意事项
- 编译器支持:确保你的编译器支持C++11标准,否则使用“=default;”会导致编译错误。例如,错误 C2065 “default”: 未声明的标识符,这通常是因为编译器没有开启C++11标准。解决方案是将编译器升级为支持该标准版本的编译器,或者将默认构造函数复原,除去“=default;”,改为“{};”。
- 函数类型限制:只能对类的特殊成员函数使用“=default;”,且该函数不能有默认参数。
Deleted函数的注意事项
- 首次声明删除:必须在函数第一次声明的时候将其声明为deleted函数,否则编译器会报错。
- 访问性问题:deleted函数通常声明为
public而不是private。当客户端代码试图调用成员函数时,C++会在检查deleted状态前检查它的访问性。如果函数是private的,编译器可能会只报告访问性错误,而不会提到函数已被删除。
常见错误示例
错误使用Defaulted函数
class B {
public:
// 错误,func不是特殊成员函数
int func() = default;
// 错误,构造函数B(int, int)不是特殊成员函数
B(int, int) = default;
// 错误,构造函数B(int=0)含有默认参数
B(int = 0) = default;
};
错误使用Deleted函数
class X {
public:
X(const X&);
};
// 错误,deleted函数必须在函数第一次声明处声明
X::X(const X&) = delete;
总结
C++11的defaulted和deleted函数为程序员提供了强大而灵活的工具,能够更精细地控制类的特殊成员函数的生成和使用。通过合理使用这两个特性,可以提高代码的可读性、可维护性和安全性,避免不必要的错误和资源浪费。在实际编程中,我们应该根据具体的需求,灵活运用defaulted和deleted函数,让代码更加健壮和高效。
- 点赞
- 收藏
- 关注作者
评论(0)