c++ default和delete

C++中,编译器会为自定义类型生成默认的构造函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。程序员可以通过`=default`显式指定生成默认版本,以保持POD类型。同时,`=delete`可以用来禁用某些函数,如拷贝构造函数,以防止不期望的行为。这提供了更精细的控制和避免错误的方式。

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

默认函数

C++中,声明自定义的类型之后,编译器会默认生成一些成员函数,这些函数被称为默认函数。其中包括:

  1. (默认)构造函数
  2. 拷贝(复制)构造函数
  3. 拷贝(复制)赋值运算符
  4. 移动构造函数
  5. 移动赋值运算符
  6. 析构函数
class base
{
public:
    base() {}; // 默认构造函数
    base(const base& other) { data = other.data; } // 拷贝构造函数
    base& operator=(const base& other) { data = other.data; return *this; } // 拷贝赋值构造函数
    base(base&& other) { data = other.data; } // 移动构造函数
    base& operator=(base&& other) { data = other.data; return *this; } // 移动赋值构造函数
    ~base() {} // 析构函数

private:
    int data;
};
  • 编译器产生的这些特殊成员函数都是public和inline的
  • 拷贝构造函数的默认操作是对所有非静态成员变量进行逐个拷贝构造;
  • 拷贝赋值构造函数的默认操作是对所有非静态成员变量进行逐个拷贝赋值构造;
  • 移动构造函数的默认操作是对所有非静态成员变量进行逐个移动构造;
  • 移动赋值构造函数的默认操作是对所有非静态成员变量进行逐个移动赋值构造;
  • 对于析构函数,如果这个类有个父类且父类的析构函数是virtual的,那么自动生成的析构函数也是virtual的;
  • 需要的时候才有可能自动生成

另外,编译器还会默认生成一些操作符函数,包括

  1. operator ,
  2. operator &
  3. operator &&
  4. operator *
  5. operator ->
  6. operator ->*
  7. operator new
  8. operator delete

default

如果实现了这些函数的自定义版本后,编译器就不会去生成默认版本。大多数时候,我们需要声明带参数的构造函数,此时就不会生成默认构造函数,这样会导致类不再是POD类型,从而影响类的优化

  • "=default"函数特性仅适用于类的特殊成员函数,且该特殊成员函数没有默认参数;
  • "=default"函数既可以在类体里(inline)定义,也可以在类体外(out-of-line)定义。
#include <iostream>
using namespace std;

class Test
{
public:
    Test(int i) : data(i) {}
    int f() = default;      // err , 函数 f() 非类 Test 的特殊成员函数
    Test(int, int) = default;  // err , 构造函数 Test(int, int) 非 Test 的特殊成员函数
    Test(int = 1) = default;   // err , 默认构造函数 Test(int=1) 含有默认参数

private:
    int data;
};

int main()
{
    std::cout << std::is_pod<Test>::value << std::endl;  // 0
}

C++11中提供了新的机制来控制默认函数生成来避免这个问题:声明时在函数末尾加上”= default”来显式地指示编译器去生成该函数的默认版本,这样就保证了类还是POD类型:

#include <iostream>
using namespace std;

class Test
{
public:
    Test() = default;  // 显式指定缺省函数,该函数比用户自己定义的默认构造函数获得更高的代码效率
    Test(int i) : data(i) {}

private:
    int data;
};

int main()
{
    std::cout << std::is_pod<Test>::value << std::endl;  // 1
}

POD类型

Plain old data structure,缩写为POD,Plain代表是一种普通类型,Old体现该类型的对象可以与C兼容。
POD类型是C++语言标准中定义的一类数据结构,适用于需要明确的数据底层操作的系统中。通常被用在系统的边界处,即指不同系统之间只能以底层数据的形式进行交互,系统的高层逻辑不能互相兼容。
比如,当对象的字段值是从外部数据中构建时,系统还没有办法对对象进行语义检查和解释,这时就适用POD来存储数据。
严格讲,一种既是普通类型(Trivial Type)又是标准布局类型(Standard-layout Type)的类型。
通俗讲,一种类型的对象通过二进制拷贝(如memcpy())后还能保持数据不变可正常使用的类型。

delete

有时候可能需要限制一些默认函数的生成。
例如:需要禁止拷贝构造函数的使用。以前通过把拷贝构造函数声明为private访问权限,这样一旦使用编译器就会报错。
而在C++11中,只要在函数的定义或者声明后面加上”= delete”就能实现这样的效果(相比较,这种方式不容易犯错,且更容易理解)

  • "=delete"函数特性可用于禁用类的某些转换构造函数,从而避免不期望的类型转换;
  • "=delete"函数特性还可以用来禁用某些用户自定义的类的 new 操作符,从而避免在自由存储区创建类的对象;
  • "=delete"函数特性还可以用来禁用拷贝构造函数、拷贝赋值函数;
#include <iostream>
using namespace std;

class Test
{
public:
    Test() = default;  // 显式指定缺省函数
    Test(int i) : data(i) {}
    Test(const Test& t) = delete; // 显式删除拷贝构造函数

private:
    int data;
};

int main()
{
    Test objT1;
//    Test objT2(objT1); // 无法通过编译
}

其它应用

(1)= default和= delete 能够更加精准的控制类的默认函数版本。其中,= default同样也能在类外的定义中修饰成员函数:

class Example
{
public:
    Example() = default;
    Example(const Example&);

private:
    int data;
};

Example::Example(const Example& ex) = default;

能够为一个类实现多个版本,只要我们在头文件里声明同样的函数,而在不同的cpp文件中用不同的方法实现,当选择不同的cpp编译时产生的版本就不同。
(2)关于= delete,它不仅局限于限制生成默认函数,还能避免编译器做一些不必要的隐式数据转换:

class Example
{
public:
    Example(int i) {}
    Example(char c) = delete;
};


int main()
{
    Example ex(1);
//   Example ex1('a');  // 无法通过编译
}

这个方法也能用于普通函数:

void func(int i) {}
void func(char c) = delete;

int main()
{
    func(1);
//   func('a');   // 编译失败
}

另外,还有很多使用场景,例如,显式删除new操作符来避免在堆上分配对象、显式删除析构函数用于构建单例模式等等。

总之,default只能用于6个特殊成员函数,但delete可用于任何成员函数。

### **default delete** #### **1. 什么是 `default` `delete`?** `default` `delete` 是 C++11 引入的关键字,用于显式声明特殊成员函数的生成或禁用。它们增强了代码的清晰性控制能力。 --- ### **2. 示例代码** ```cpp #include <iostream> using namespace std; // 演示 default delete 的使用 class MyClass { private: int value; public: // 默认构造函数 MyClass() = default; // 编译器生成默认构造函数 // 拷贝构造函数 MyClass(const MyClass& other) = default; // 编译器生成拷贝构造函数 // 移动构造函数 MyClass(MyClass&& other) noexcept = default; // 编译器生成移动构造函数 // 拷贝赋值运算符 MyClass& operator=(const MyClass& other) = default; // 编译器生成拷贝赋值运算符 // 移动赋值运算符 MyClass& operator=(MyClass&& other) noexcept = default; // 编译器生成移动赋值运算符 // 析构函数 ~MyClass() = default; // 编译器生成析构函数 // 禁用某些操作 void specialFunction() = delete; // 禁用该函数 }; int main() { MyClass obj1; // 调用默认构造函数 MyClass obj2 = obj1; // 调用拷贝构造函数 MyClass obj3 = std::move(obj1); // 调用移动构造函数 obj2 = obj3; // 调用拷贝赋值运算符 // obj2.specialFunction(); // 错误:该函数已被禁用 return 0; } ``` --- ### **3. 解释** #### **(1) `default` 的作用** - `default` 关键字用于请求编译器为类生成默认实现的特殊成员函数。 - 常见的特殊成员函数包括: - 默认构造函数 - 拷贝构造函数 - 移动构造函数 - 拷贝赋值运算符 - 移动赋值运算符 - 析构函数 - 使用场景: - 当需要显式声明某个特殊成员函数由编译器生成时,可以使用 `default`。 - 示例:在上述代码中,`MyClass` 的所有特殊成员函数都被显式标记为 `default`,确保编译器为其生成默认实现。 #### **(2) `delete` 的作用** - `delete` 关键字用于禁用某个函数或特殊成员函数。 - 使用场景: - 禁用不必要的拷贝或移动操作(例如,当对象不可复制时)。 - 禁用某些用户定义的函数。 - 示例:在上述代码中,`specialFunction()` 被标记为 `delete`,因此任何尝试调用该函数的行为都会导致编译错误。 #### **(3) 使用场景** - **`default`**: - 当希望明确表示某个特殊成员函数由编译器生成时,可以使用 `default`。 - 示例:在资源管理类中,可能需要显式声明移动构造函数移动赋值运算符为 `default`。 - **`delete`**: - 当不希望某个操作被使用时,可以使用 `delete` 禁用它。 - 示例:在单例模式中,可以禁用拷贝构造函数拷贝赋值运算符。 #### **(4) 注意事项** - `default` `delete` 只能用于特殊成员函数或用户定义的函数。 - 如果一个类中有自定义的特殊成员函数(如拷贝构造函数),则编译器不会自动为其生成其他特殊成员函数(如移动构造函数)。此时可以显式使用 `default` 来请求生成。 --- ### **4. 总结** `default` `delete` 是 C++11 提供的重要工具,用于增强对特殊成员函数的控制能力。`default` 明确声明编译器生成默认实现,而 `delete` 则用于禁用不需要的操作。合理使用这两个关键字可以帮助开发者编写更清晰、更安全的代码。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值