C++11新特性介绍(四)

前言

该篇文章讲述C++11新特性中

①继承构造

②委托构造

③移动构造

④移动赋值

继承构造

继承构造,就是子类继承父类的构造函数,我们都知道一个子类继承父类,子类构造函数初始化成员变量时需要同时初始化父类的成员,例如

B继承A,B构造函数使用初始化列表给父类传参数

class A
{
public:
  A(int x,int y)
  {
     a = x;
     b = y;
  }
  int a;
  int b;
};
class B:public A
{
public:
  B(int x,int y):A(x,y){}
};

这样写没什么问题,但仔细观察发现B除了继承A的成员变量外,没有新增成员变量,其实只需要初始化A类中的成员变量,但A中已经写过一次构造了,B中再这样写就多余了,此时就可以使用继承构造,让B继承A的构造函数,可以将B类改写成

class B:public A
{
public:
  using A::A; //继承构造
};
int main()
{
  B b(10,20);//创建B类对象,给父类传参
}

当然在创建B类的对象时还是需要给父类传参数

继承构造注意点:

①C++11允许派生类继承基类的构造函数(默认构造函数,复制构造函数,移动构造函数除外)

②继承构造函数只能给基类的成员变量初始化(所以如果派生类新增加成员,则还是用传统方法写,继承构造就没太大意义了)

③基类构造函数不能是私有的,派生类不能虚继承基类,这样都是不能用继承构造的

④派生类使用继承构造,则编译器不会再提供默认构造

委托构造

委托构造是C++11对C++构造函数的改进,它允许构造函数通过初始化列表调用同一个类的其他构造函数,即可以调用其他构造函数来执行自己的初始化过程

例如不使用委托构造,当类中重载多个构造函数时

class A
{
public: //不使用委托构造,多个构造函数
	A(){ 
	m_num = 0;
	m_price = 0;
	m_name = "";}
	A(int num, int price,string name):m_num(num), m_price(price), m_name(name){}
    A(int num,string name):m_num(num), m_price(50), m_name(name){}
	int m_num; //书号
	int m_price;//价格
	string m_name;//书名
};

默认构造函数以及2参数构造函数委托3参数构造函数来执行自己的初始化过程,这样不论如何重载构造函数,都委托给一个构造函数,这样维护方便,效率更高

class A
{
public: //使用委托构造
	A(int num, int price,string name):m_num(num), m_price(price), m_name(name){}
	A():A(0,0,""){}
    A(int num,string name):A(num,50,name){}
	int m_num; //书号
	int m_price;//价格
	string m_name;//书名
};

注意,当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始化列表和函数体被依次执行,即上面的默认构造函数委托的是3参数的构造函数,3参数构造函数(受委托)的函数体恰好是空的,假如不是空的,那么会先执行这些代码,然后控制权才会回到默认构造函数(委托者)的函数体,即调用2参数有参构造时,会先执行3参数的函数体,然后再执行2参数的函数体,并且委托不要搞成委托环

移动构造

C++11引入了对象移动而非拷贝的概念,有时候对象发生拷贝后就被销毁了,这种情况下移动而非拷贝对象会大幅度提升性能,可以把它和拷贝构造函数作比较

拷贝构造函数相当于对象和被拷贝的副本分别在大小相同但位置不同的堆内存中,比如当我们需要返回一个类对象时,就是创建临时对象,最后return的时候先拷贝一个副本,然后析构临时对象,最后返回这个副本,加上最后再析构这个副本,析构需要调用2次

其实这个临时对象拷贝完后,已经不需要了,但因为我们拷贝副本新开辟了内存,需要把这个临时对象的空间释放,能不能不开辟新空间,也拷贝副本呢,答案是可以的!就是使用移动构造

先提一下C++引入的移动语义:源对象资源的控制权全部交给目标对象

移动构造函数相当于临时对象原本控制的内存转移给了副本,相当于把它移动过去了,这样就不需要开辟新的内存,也减少了析构的次数,提升了效率

class A
{
public: 
	A(A&& str) //移动构造
	{
		m_num = str.m_num; //拷贝地址,没有重新申请内存
		m_name = str.m_name;
	}
	int m_num; 
	string m_name;
};

有几点需要注意:

①参数(右值)的符号必须是右值引用符号,即“&&”

②参数(右值)不可以是常量,因为我们需要修改右值

③参数(右值)的资源链接和标记必须被修改,否则,右值的析构函数就会释放资源,转移到新对象的资源也就无效了

什么场景适合使用移动构造?

①如果临时对象即将消亡,但它里面的资源是需要被再利用的,适合使用移动构造

②对于需要动态申请大量资源的类,应该设计移动构造函数和移动赋值函数,以提高程序的效率

移动赋值

移动赋值函数类比的是赋值运算符的重载,这两者的关系和上面讲到的拷贝构造和移动构造一样,移动赋值也不会开辟新的内存,仅仅是移动数据成员,第一个参数也是右值引用

在自定义移动赋值运算符时,需要检查是否存在自赋值,也就是说如果要赋值的对象与自己的地址一样,则不需要做任何事情

class A
{
public:
   string m_name;
   A& operator=(A&& str) //移动赋值函数
    {
        if (this != &str)  //如果赋值对象与自身地址一致,则直接返回自身
        {
            m_name= str.m_name;
        }
        return *this;
    }
}

移动操作注意事项(移动构造、移动赋值等)

移动操作必须确保移动后源对象可以被销毁且销毁后不会影响新创建的对象,例如如果源对象中有数据成员是指针,则必须置为空,否则在源对象执行析构函数时,会将新创建对象中的指针指向的资源释放掉

这篇文章对你有帮助的话~就点个赞吧~

点赞收藏关注就是对我最大的支持~

猜你喜欢

转载自blog.csdn.net/m0_71741835/article/details/127679231