“深入理解C++类默认成员函数:探索构造、析构与复制“

编程入门 行业动态 更新时间:2024-10-25 01:32:36

“深入理解C++类默认成员<a href=https://www.elefans.com/category/jswz/34/1771370.html style=函数:探索构造、析构与复制“"/>

“深入理解C++类默认成员函数:探索构造、析构与复制“

文章目录

  • 类的8个默认成员函数
    • 构造函数
    • 析构函数
    • 拷贝构造
    • 移动构造
    • 赋值运算符重载
    • 移动赋值运算符重载
    • 取地址及const取地址操作符重载
  • 强制生成默认函数的关键字default
  • 禁止生成默认函数的关键字delete

类的8个默认成员函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下8个默认成员
函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
class Date {};

C++类的默认成员函数包括构造函数、析构函数、拷贝构造函数、移动构造函数、赋值运算符和移动赋值运算符;构造函数用于初始化,析构函数用于清理;拷贝构造函数和移动构造函数均可用于同类对象的初始化;赋值运算符和移动赋值运算符则用于将一个对象赋值给另一个对象;取地址运算符主要用于普通对象和const对象取地址,且很少需要自己实现。


构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。

构造函数特征如下:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 整个生命周期内只调用一次。

class Date
{
public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};void TestDate()
{Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3();
}

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
但是,如果你已经定义了一个构造函数(无论是否有参数),编译器就不会再自动生成默认构造函数了。这意味着你需要自己提供所有的构造函数定义,包括无参构造函数。

class Date
{
public:/*// 如果用户显式定义了构造函数,编译器将不再生成Date(int year, int month, int day){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;
}

关于编译器生成的默认成员函数,很多同学会有疑惑:不实现构造函数的情况下,编译器会
生成默认的构造函数。但是看起来默认构造函数又没什么用?对象调用了编译器生成的默
认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??

解答:

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类
型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员
函数。

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

注意:

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。

class Date
{
public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{Date d1;
}

解答:

  1. 以上程序不能通过编译.因为构造函数冲突,无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。以上程序包含了多个构造函数.

初始化列表
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
在上面的构造函数代码中,我们都是在函数体内初始化的,其实构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值
所有我们就产生了初始化列表

  • 初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。

class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};

C++11的缺省值,这个缺省值是给初始化列表的

  1. 如果初始化列表没有显示给值,就用这个缺省值
  2. 如果给值,就不用这个缺省值

初始化列表是每个成员定义的地方,不管你写不写每个成员都要走初始化列表

  • 如果不写初始化列表
    自定义类型调用自己的构造
    内置类型给成随机值
  • 如果显示写初始化列表
    调用给定的参数

注意:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
  1. 引用成员变量
  2. const成员变量
  3. 自定义类型成员(且该类没有默认构造函数时)
    4
  1. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
    一定会先使用初始化列表初始化。

class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:B(int a, int ref):_aobj(a), _ref(ref), _n(10){}
private:A _aobj;  // 没有默认构造函数int& _ref;  // 引用const int _n; // const 
};

  1. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
    次序无关.如果不按该规则可能会出现不必要的麻烦.
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2;int _a1;
};
int main() {A aa(1);aa.Print();
}
A.输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值

正确答案是D.编译不通过。

在构造函数中,初始化列表的顺序应该与变量声明的顺序相同,否则会出现不必要的麻烦。在上面的代码中,变量 _a1 和 _a2 的声明顺序是 _a2 和 _a1,但是初始化列表中的顺序是 _a1 和 _a2,这会导致a2初始化成a1没有赋值前的随机值。因此,选项D是正确的。


析构函数

我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
    函数不能重载.
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){_array = new DataType[capacity];_capacity = capacity;_size = 0;}//------------------------------------------------------------~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};

  1. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器
    生成的默认析构函数,对自定类型成员调用它的析构函数。
class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

程序运行结束后输出:~Time()

在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?

  • 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
    _day三个是 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在 d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。
    但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁, main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
    注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数.

  1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
    Date类;有资源申请时,一定要写,否则会造成资源泄漏。

拷贝构造

在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

拷贝构造函数

只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

特征:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
    因为会引发无穷递归调用。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d)   // 正确写法Date(const Date& d)   // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
}


  1. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
    字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

注意

在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。


  1. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
    当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

typedef int DataType;
class Stack
{
public:/*注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。*/Stack(size_t capacity = 10){_array = new DataType[capacity];_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){delete _array;_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。


  1. 拷贝构造函数典型调用场景:
  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}


为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用。


移动构造

如果临时对象即将消亡,并且它里面的资源需要被再利用的时候,就可以触发移动构造。
什么时候该触发移动构造?临时对象即将消亡,且它里面的资源需要被再利用(有可被利用的临时对象)。

它用于将资源从一个对象转移到另一个对象,而不需要进行复制操作。这在处理大型对象或具有大量资源的对象时特别有用,因为它可以避免不必要的内存分配和复制操作,从而提高程序的性能。

Date(Date&& d)
  1. 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
    意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
    型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
    如果实现了就调用移动构造,没有实现就调用拷贝构造。
  2. 拷贝对象需要深拷贝时,自己写移动构造.
  • 移动构造函数的语法如下:
class MyClass {  
public:  MyClass(MyClass&& other) {  // 移动资源  }  
};

其中,MyClass&& other是一个右值引用,表示要被移动的对象。在移动构造函数中,你可以将其他对象的资源转移到新对象中,而不必复制这些资源。


下面是一个简单的示例,展示了如何使用移动构造函数

#include<string>
#include<iostream>
using namespace std;
namespace XmLife
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(100){cout << "string() --构造" << endl;_str = new char[_capacity];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 移动构造string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 资源转移" << endl;swap(s); //转移资源}~string(){delete[] _str;_str = nullptr;}void push_back(char ch){_str[_size] = ch;++_size;_str[_size] = '\0';}private:char* _str;size_t _size;size_t _capacity;};
}XmLife::string get_string()
{XmLife::string str;str.push_back(1);str.push_back(2);str.push_back(3);return str;
}// 拷贝构造和移动构造
int main()
{XmLife::string ret = get_string();  //移动构造XmLife::string s1("1111111"); //构造XmLife::string s2(s1); //拷贝构造return 0;
}

get_string()函数返回值是 string,如果大型对象或具有大量资源的对象时,如果使用string传值返回会调用拷贝构造,浪费了内存分配和复制操作,但又因为str是局部对象,出了作用域就销毁了,又不能传引用返回。这时我们的移动构造的特点就来了.他相当于把你之前的资源盗取过来,在把空的资源给你销毁掉。

  1. 因为str是即将消亡,且它里面的资源需要被再利用,在你return str时候会调用移动构造。
  2. 在移动构造里面他会把你str里面的值和自己的内容交换,交换完成后str是空值了,并且返回str时候会销毁.
  3. 注意: 如果没有移动构造会调用拷贝构造来完成,从而性能会下降。

移动构造函数的作用是避免不必要的内存分配和复制操作,从而提高程序的性能。它可以将一个对象的资源直接转移给另一个对象,而不是通过复制操作创建新的对象。移动构造函数通常使用右值引用作为参数,接收一个将要被移动的对象,并且在移动过程中将原对象的资源设置为空或无效,同时将资源交给新对象。这种机制可以避免不必要的内存分配和复制操作,从而提高程序的性能。特别是在处理大型对象或具有大量资源的对象时,移动构造函数的作用更加显著。


赋值运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似.

函数名字为:

关键字operator后面接需要重载的运算符符号。

函数原型:

返回值类型 operator操作符(参数列表)

注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
    藏的this
  5. .* :: sizeof ?: . 注意以上5个运算符不能重载。
  • 赋值运算符重载

赋值运算符重载格式

  1. 参数类型:const T&,传递引用可以提高传参效率
  2. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  3. 检测是否自己给自己赋值
  4. 返回*this :要复合连续赋值的含义

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};

  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数。

我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数。


  1. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
    意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
    重载完成赋值。

当然跟上面拷贝构造一样,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。


移动赋值运算符重载

移动赋值运算符的作用是将一个对象的资源转移到另一个对象中,并使原对象处于有效状态。

移动赋值运算符的重载语法如下:

class MyClass {  
public:  MyClass& operator=(MyClass&& other) {  // 移动资源  return *this;  }  
};

其中,MyClass&& other是一个右值引用,表示要被移动的右操作数对象。在移动赋值运算符中,我们需要将其他对象的资源转移到当前对象中,并使原对象处于有效状态。


  1. 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
    的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
    置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
    值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
    完全类似)
  2. 移动赋值对象需要深拷贝时,自己写移动赋值重载函数.

移动赋值操作通过移动语义实现对象的转移,避免了不必要的数据拷贝,提高了性能。移动赋值会在以下情况下被触发:

  1. 当右值(临时对象或具有右值引用的对象)与左值(具有左值引用的对象)进行赋值操作时。
  2. 当使用std::move函数将左值转换为右值引用后进行赋值操作时。
  3. 当返回右值引用的函数的结果被赋值给目标对象时。
  4. 当涉及到内置类型时,移动赋值并不适用,因为内置类型的对象直接存储其值,不涉及资源的转移和释放。
  • 内置类型
int main()
{//内置类型int a = 10;int b = std::move(a); // b 现在拥有 a 的值int* p = new int(10);int* q = std::move(p); // q 现在拥有 p 所指向的内存*p = 30;cout << *p  <<  " ||| " << *q << endl; //30 ||| 30std::vector<int> vec1;vec1.push_back(1);std::vector<int> vec2 = std::move(vec1); // vec2 现在拥有 vec1 的所有权,vec1 是空的
}
  • 自定义类型
#include <iostream>
#include <string>class MyClass {
private:std::string data;public:// 构造函数MyClass(const std::string& value) : data(value) {std::cout << "构造函数,值为 " << data << std::endl;}// 移动构造函数MyClass(MyClass&& other) noexcept : data(std::move(other.data)) {std::cout << "移动构造函数,值为 " << data << std::endl;}// 移动赋值运算符MyClass& operator=(MyClass&& other) noexcept {if (this != &other) {data = std::move(other.data);std::cout << "移动赋值运算符,新值为 " << data << std::endl;}return *this;}// 析构函数~MyClass() {std::cout << "析构函数,值为 " << data << std::endl;}// 输出值void print() const {std::cout << "值为 " << data << std::endl;}
};int main() {MyClass obj1("Hello");MyClass obj2("World");obj1 = std::move(obj2);obj1.print();obj2.print();return 0;
}输出结果::
构造函数,值为 Hello
构造函数,值为 World
移动赋值运算符,新值为 World
值为 World
值为
析构函数,值为
析构函数,值为 World

使用 std::move 将 obj2 移动赋值给 obj1,从而触发了移动赋值操作。在移动赋值运算符中,我们通过使用 std::move 将 other 的数据成员的值转移到了 data 中,实现了资源的转移。最后,我们分别输出了 obj1 和 obj2 的值,在 obj1 中打印出了新值 “World”,而 obj2 的值为空。


取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{
public:Date* operator&(){return this;}const Date* operator&()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!


强制生成默认函数的关键字default

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用default关键字显示指定移动构造生成。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name), _age(p._age){}Person(Person&& p) = default;
private:string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

禁止生成默认函数的关键字delete

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁
已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即
可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;
private:string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

更多推荐

“深入理解C++类默认成员函数:探索构造、析构与复制“

本文发布于:2023-12-03 23:43:11,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1659076.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:函数   成员

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!