首页 > 编程语言 >C++特种成员函数生成机制及相关原则

C++特种成员函数生成机制及相关原则

时间:2023-10-03 10:45:00浏览次数:47  
标签:特种 函数 C++ 运算符 复制 赋值 BaseClass 构造函数

C++特种成员函数生成机制及相关原则

注:默认C++标准是C++11及以后的标准,因为C++11之前的标准定义的默认成员函数不包含移动构造函数和移动赋值运算符

1. C++默认成员函数

默认成员函数的定义:

类中没有显示声明,在需要时编译器自动生成的函数,包括默认构造函数默认析构函数默认复制构造函数默认复制赋值运算符默认移动构造函数默认移动赋值运算符共计6种。

基础类定义:

class BaseClass {};

1.1 构造函数

对于任何一个class而言构造函数都是必须,因为每一个类对象的构造都会调用构造函数。

BaseClass();

1.1.1 生成条件

当且仅当,类中没有显式声明任何构造函数(包括任何形式的有参构造)时,编译器会为该类自动生成默认构造函数。

1.1.2 属性

属性 结果
访问权限 public
inline yes
virtual no

另外,C++允许构造函数重载,当类的实现已经有了对于构造函数的声明时,可以通过BaseClass() = default的方式保留编译器自动生成默认构造函数的机会,在需要的时候编译器仍然可以自动生成。

1.2 析构函数

析构函数在类对象生命周期结束时被调用,负责对象资源的销毁,所以析构函数对于任何一个class而言都是必须的。

~BaseClass();

1.2.1 生成条件

当且仅当,类中没有显式声明析构函数时,编译器会为该类自动生成默认析构函数。

1.2.2 属性

属性 结果
访问权限 public
inline yes
virtual 当基类的析构函数为虚时才有virtual属性
noexcept yes

关于virtual属性的代码示例:

如下情况时,编译器为BaseDeriver类自动生成的析构函数都为非虚析构函数。

class Base{};

class Derive: public Base{};

如下情况时,编译器为Deriver类自动生成的析构函数为虚析构函数。

class Base
{
public:
    virtal ~Base(){};
};

class Derive: public Base{};

如下情况时,编译器为DeriverADeriverB类自动生成的析构函数都为虚析构函数。

class Base
{
public:
    virtal ~Base(){};
};

class DeriveA: public Base{};

class DeriveB: public DeriveA{};

另外,C++不允许析构函数重载,所以显式声明析构函数和~BaseClass() = default只能存其一。

1.3 复制操作

复制是指同一个类的不同对象之间的赋值行为,都是按成员进行非静态成员的复制构造。在C++中有默认赋值构造函数和默认复制赋值运算符,两者的自动生成机制相互独立,声明一个并不会组阻止编译器自动生成另一个。

1.3.1 默认复制构造函数

复制构造函数的基本形式如下,表示用一个对象构造另外一个同类对象。

BaseClass(BaseClass&);

如下代码中,b2b3对象的构造都会调用复制构造函数。

BaseClass b1{};
BaseClass b2{b1};
BaseClass b3(b1);
1.3.1.1 生成条件
  1. 当且仅当,类中未显式声明复制构造函数并且代码中出现了调用复制构造函数的场景时编译器才会为该类自动生成默认复制构造函数。

  2. 如果该类中声明了移动操作,则默认复制构造函数将被删除。

  3. 如何该类中声明了复制赋值运算符或析构函数,仍然生成默认复制构造函数被认为是废弃行为。

1.3.1.2 属性
属性 结果
访问权限 public
inline yes
virtual no
explicit no

另外,当显式声明复制构造函数时,该函数的参数列表必须是BaseClass&的形式。必须是引用的原因是:如果没有引用那么实参向形参的赋值过程中为值传递,就会调用复制构造函数,而复制构造函数的参数本来就是值传递又会调用复制构造函数,这样会导致无限递归下去,而引用的传递则不会调用复制构造函数。

1.3.2 默认复制赋值运算符

复制赋值运算符基本声明如下,表示同类不同对象之间的数据拷贝。

BaseClass& operator=(BaseClass&);

如下代码中,b2 = b1过程会调用复制赋值运算符。

BaseClass b1{};
BaseClass b2{};
b2 = b1;
1.3.2.1 生成条件
  1. 当且仅当,类中未显式声明复制赋值运算符并且代码中出现了调用复制赋值运算符的场景时编译器才会为该类自动生成默认复制赋值运算符。

  2. 如果该类中声明了移动操作,则默认复制赋值运算符将被删除。

  3. 如果该类中声明了复制构造函数或析构函数,仍然生成默认复制赋值运算符被认为是废弃行为。

1.3.2.2 属性
属性 结果
访问权限 public
inline yes
virtual no

另外,当显式声明复制赋值运算符时,该函数的参数列表必须是BaseClass&的形式,原因同复制构造函数。

1.4 移动操作

移动操作用于资源控制权的移交,具体过程则是按成员进行非静态成员的移动构造,以达到减少数据的复制的目的,从而提高程序运行效率。在C++中有默认移动构造函数和默认移动赋值运算符,两种移动操作不相互独立,声明其中一个就会阻止编译器生成另一个。

1.4.1 移动构造函数

移动构造函数的基本形式如下,表示用一个对象移动构造另外一个同类对象。

BaseClass(BaseClass&&);

如下代码中,b2b3对象的构造都会调用移动构造函数。

BaseClass b1{};
BaseClass b2{std::move(b1)};
BaseClass b3(std::move(b2));
1.4.1.1 生成条件

当且仅当,类中没有声明复制操作(复制构造函数和复制赋值运算符)、移动操作(移动赋值运算符)和析构函数并且出现会调用移动构造函数场景时,编译器会为该类自动生成默认移动构造函数。

1.4.1.2 属性
属性 结果
访问权限 public
inline yes
virtual no
explicit no

1.4.2 移动赋值运算符

移动赋值运算符的基本形式如下,表示将实参所拥有的资源转移给当前对象。

BaseClass& operator=(BaseClass&&);

每当重载决议选择移动赋值运算符时,该函数会被调用。例如=左侧为可赋值的左值,而右侧则为同类型或可隐式转换为同类型的可移动的右值,示例如下:

BaseClass b1{};
BaseClass b2{};
b1 = std::move(b2);
1.4.2.1 生成条件

当且仅当,类中没有显式声明复制操作(复制构造函数和复制赋值运算符)、移动操作(移动构造函数)和析构函数并且出现会调用移动构造函数场景时,编译器会为该类自动生成默认移动构造函数。

1.4.2.2 属性
属性 结果
访问权限 public
inline yes
virtual no

2. 相关的编码原则

2.1 三之原则

如果一个类需要用户自定义析构函数、复制构造函数或复制赋值运算符,那么该类几乎需要全部三者。

2.2 五之原则

用户自定义的析构函数、复制构造函数和复制赋值运算符会阻止默认移动构造函数和移动赋值运算符的自动生成,所以如果一个类需要用户定义移动操作,那么该类就需要用户自定义全部五个特殊成员函数。

2.3 零之原则

有自定义析构函数、复制操作函数和移动操作函数的类应该专门处理所有权,其他类都不应该拥有自定义的析构函数、复制操作函数和移动操作函数。

参考内容

  1. 《Effectivate Modern C++》
  2. https://zh.cppreference.com/w/cpp/language/rule_of_three

标签:特种,函数,C++,运算符,复制,赋值,BaseClass,构造函数
From: https://www.cnblogs.com/baiweituyou/p/17740878.html

相关文章

  • 基于hash_table对STL unordered系列容器的封装 #C++
    概述本文对hash_table进行封装,以模仿SGISTL对unordered系列容器进行简单实现,旨在加深对C++封装与泛型技法的体会与理解。阅读本文之前,建议先对哈希表进行学习。unordered_map与map一样,unordered_map的所有元素类型都是pair,pair的第一个成员为Key,第二个成员为Value。因为Key在任何......
  • Shell 函数详解(函数定义、函数调用、参数变量)
    Shell函数的本质是一段可以重复使用的脚本代码,这段代码被提前编写好了,放在了指定的位置,使用时直接调取即可。Shell中的函数和C++、Java、Python、C# 等其它编程语言中的函数类似,只是在语法细节有所差别。Shell函数定义的语法格式如下:functionname(){statements[re......
  • C++ 对拍详解 和解读
    对拍是什么#​对拍,是一个比较实用的工具。它能够非常方便地对于两个程序的输出文件进行比较,可以帮助我们实现一些自动化的比较输出结果的问题。​众所周知,几乎每一道编程题目,都会有某种正解能拿到满分;当我们想不出正解时,我们往往可以打暴力代码来获取部分分数。​但是,当我们觉......
  • C++类内存分布+ Studio工具
    书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承、虚函数存在的情况下。工欲善其事,必先利其器,我们先用好VisualStudio工具,像下面这样一步一步来:  先选择左侧的C/C++->命令行,然后在其他选项这里写上......
  • C++ STL快速入门方法
    在数月之前的机试中第一次体验到STL的威力,因为自己本来一直在用C语言做开发,很多数据结构都是自己造的,比如链表、队列等,第一次接触C++STL后发现这些数据结构都已经给我提供好了,我直接拿去调用就好了,真是超级方便。最近的项目中也遇到了STL一些容器,所以现在自己好好总结一下STL中......
  • C++模板元编程(C++ template metaprogramming)
    实验平台:Win7,VS2013Community,GCC4.8.3(在线版) 所谓元编程就是编写直接生成或操纵程序的程序,C++模板给C++语言提供了元编程的能力,模板使C++编程变得异常灵活,能实现很多高级动态语言才有的特性(语法上可能比较丑陋,一些历史原因见下文)。普通用户对C++模板的使用可能不是很......
  • C++ STL 一般总结
    以下内容来源网上经过整合而成一、一般介绍     STL(StandardTemplateLibrary),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++StandardLibrary)中,是ANSI/ISOC++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里......
  • 十四天学会C++之第一天(入门和基本语法)
    C++的起源和历史C++诞生于20世纪80年代初,它的创造者是计算机科学家BjarneStroustrup。当时,Stroustrup在贝尔实验室工作,他希望为C语言添加一些功能,以便更好地支持系统开发。这个愿望促使他创建了C++。C++的名字来源于它的基因,其中的"C"代表了C语言,而"++"表示C语言的一个增强版本。......
  • 函数指针与回调函数
    (目录)1.函数指针前面我们学的:整形指针是指向整形的指针字符指针是指向字符的指针数组指针是指向数组的指针所以函数指针就是指向函数的指针假如有一个int类型变量a,要取它的地址就是&a,有一个字符类型变量c,要取它的地址就是&c,那么一个函数的地址是怎样取到的呢。接下来,我......
  • python 3 内嵌函数和闭包
    内嵌函数:本质是函数里又嵌套一个函数def fun1():       print('fun1()在被调用')       def fun2():               print('fun2()在被调用')    fun2()  #调用fun2()  fun1()fun1()正在被调用fun2()正在被调用内嵌函数的作......