C++ Primer第五版笔记——构造函数与拷贝控制

虚析构函数:

继承关系对基类拷贝控制最直接的影响是基类通常应该定义一个虚析构函数,这样就能动态分配继承体系中的对象了。
当delete一个动态分配的对象的指针时将执行析构函数,如果该指针指向继承体系中的某个类型,则有***可能出现指针的静态类型与被删除对象的动态类型不符的情况***,例如:删除一个基类指针,而该指针实际指向的是子类的对象,那么编译器应该要知道在执行析构函数的时候是执行的子类的虚构函数。因此***通过将析构函数定义为虚函数来确保执行正确的析构函数***。
和其他虚函数一样,析构函数的虚属性也会被继承,因此,只要将基类的析构函数定义为虚函数,就能确保delete基类指针时执行正确的析构函数版本。
虚析构函数将阻止合成移动操作,即一个类定义了析构函数,那么就算他通过=default的形式使用合成的版本,编译器也不会为这个类合成移动操作。

派生类中删除的拷贝控制与基类的关系:

某些定义基类的方式可能导致有的派生类成员成为被删除的对象:
1.如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或者析构函数是被删除的函数或者是不可访问的,则派生类中***对应的成员***将是被删除的,原因是编译器不能使用基类成员来执行派生类对象中基类部分的构造、赋值或销毁操作;
2.如果在基类中有一个不可访问或删除的析构函数,则派生类中合成的默认和拷贝构造函数将是被删除的,因为编译器无法销毁派生类对象的基类部分;
3.和之前一样,编译器不会合成一个删除掉的移动操作。 当使用=default操作请求一个移动操作时,如果基类中对应的操作时被删除的或者是不可访问的,那么派生类中该函数将是被删除的,原因是派生类中基类部分不可移动,同样,如果基类的析构函数是删除的或是不可访问的,则派生类的移动构造函数也将是被删除的。
举个例子:

class B{
public:
	B();
	B(const B&) = delete;
};
class D:public B{
//没有声明任何构造函数
};
D d;			//正确:D的合成默认构造函数使用B的合成默认构造函数
D d2(d);		//错误:D的合成拷贝构造函数是被删除的
D d3(std::move(d));		//错误,隐式的使用D的被删除的拷贝构造函数

B中有可访问的默认构造函数数和被删除的拷贝构造函数。因为定义了拷贝构造函数,所以编译器不会为B合成默认的移动构造函数,因此既不能移动也不能拷贝B的对象。如果B的派生类希望自己的对象能被移动和拷贝,需要自己定义相应版本的构造函数

移动操作与继承:

大多数基类都会定义一个虚析构函数,因此在默认情况下,基类通常不含有合成的移动函数,而且它的派生类中也没有合成的移动操作。
因为***基类缺少移动操作会阻止派生类拥有自己的合成移动操作***,所以当确实需要执行移动操作时***应当首先在基类中定义***。

派生类的拷贝控制成员:

派生类的构造函数在初始化自己部分的时候也要初始化基类的部分,类似的,派生类的拷贝和移动构造函数在拷贝和移动自己的成员的时候,也要拷贝和移动基类部分的成员,派生类的赋值运算符也需要为其基类部分赋值。
而***析构函数***不同,它***只负责销毁派生类自己分配的资源***,对象的成员是被隐式销毁的,派生类的基类部分也是自动销毁的。对象销毁的顺序与其创建的顺序相反。

定义派生类的拷贝或移动构造函数:

当为派生类定义拷贝或移动构造函数时,通常使用对应的基类的构造函数初始化派生类对象的基类的部分:

class B{/*...*/};

class D:public B{
public:
	D(const D& d):Base(d){/*...*/}
	D(D&& d):Base(std::move(d)){/*...*/}
};

派生类赋值运算符:

与拷贝和移动构造函数一样,派生类的赋值运算符也必须显示的为派生类对象的基类部分赋值:

D& D::operator=(const D& rhs){
	Base::operator=(rhs);          //为基类部分赋值
	/*......*/
	return *this;
}

“继承”的构造函数:

在C++11新标准中,派生类能够重用其直接基类定义的构造函数,因为***一个类只初始化它的直接基类,所以一个类也只继承其直接基类的构造函数***。类***不能继承默认、拷贝和移动构造函数***,如果派生类没有直接定义这些构造函数,编译器将为派生类合成它们。
派生类继承基类的构造函数的方式是***提供一条注明了直接基类名的using声明语句***。通常情况下,using声明语句只是令某个名字在当前作用域可见,但当作用于构造函数的时候,using语句将令编译器产生代码。对于基类的每一个构造函数,编译器都生成一个与之对应的派生类的构造函数,即对于基类的每个构造函数,在派生类中都有一个***形参列表完全相同***的构造函数。

继承的构造函数的特点:

与普通成员的using声明不同,一个构造函数的using声明不会改变该构造函数的访问等级。例如,不管using声明出现在哪里,基类的私有构造函数在派生类中还是一个私有构造函数。
一个using声明语句不能指定***explicit***(声明为explicit的构造函数不能在隐式转换中使用)或***consexpr***(constexpr表示一个函数或者表达式可以在编译时就求出值,constexpr构造函数的函数体一般为空,使用初始化列表或者其他的constexpr构造函数初始化所有数据成员。)。如果基类的构造函数是explicit或consexpr的,则在派生类中继承来的构造函数也有同样的属性。
当一个基类构造函数含有默认实参时,这些实参并不会被继承,派生类将获得***多个***继承的构造函数,其中每个构造函数分别省略掉一个含有默认实参的形参,例如,当基类的构造函数有两个形参,其中第二个形参含有默认实参,则派生类将获得两个继承的构造函数:其中一个构造函数接受两个参数(没有默认形参);另一个构造函数只接受一个参数,既基类中没有默认值的那个形参。
如果基类中含有多个构造函数,大多数时候派生类会继承所有这些构造函数,但是有两个例外:一个是派生类可以继承一部分构造函数,而为其他构造函数定义自己的版本。如果派生类定义的构造函数与基类中的构造函数具有相同的参数列表,则该构造函数不会被继承。
另一个例外是默认、拷贝和移动构造函数不会被继承,这些函数按照正常的规则被合成。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值