C++的继承和大部分的语言不同的地方在于它的继承也有访问权限。通常来说用得比较广泛的两种是,公有继承和私有继承。虽然他们都是继承,但是实际上有非常本质的区别。《Effctive C++》中有提到,私有继承实际上是has-a
的关系,而公有继承才是真正传统意义上的继承,也就是is-a
的关系。
我们常说C++的动多态使用继承来实现,但是实际上更准确一点的说法应该是,C++中的动多态使用公有继承来实现,因为私有继承本质上来说是组合关系而不是继承关系,我们没有办法用父类的引用指向一个私有继承的子类对象。也就是说下面这段代码实际上无法通过编译。
1 | class Base { |
这个错误其实并不难,因为编译时期就会报错,不会有太大的问题。我最近碰到一个编译可以正常通过,但是运行的时候会抛异常的BUG,花了一些时间定位,记录在这。
std::enable_shared_from_this
在C++11中,我们常常使用智能指针来避免资源泄露或者野指针问题,其中基于引用计数实现的智能指针std::shared_ptr
有一个非常有趣的功能是:它可以实现获取this
的智能指针。
我们知道,每个成员函数,都默认会有一个this
参数指向当前对象的地址。但是这个指针是一个裸指针,使用不当会有野指针的风险,比如下面这段代码。
1 | std::vector<std::function<void(void)>> actions; |
在Widget::RegisterAction
中,我们把this指针传递给了std::bind
,生成了一个std::function
放在全局的actions
中(我并不推荐使用全局变量,这里只是为了方便演示而已,实际代码中尽量不用全局变量),这个std::function
对象的生命周期比widget
本身要长,所以在main
函数中,会产生野指针崩溃。
为了避免野指针,我们第一个想到的就是智能指针,搭配std::enable_shared_from_this
,它可以获取到this
的指针指针。所以上面这一段代码可以这样更改。
1 | class Widget : std::enable_shared_from_this<Widget> { // 1 |
注意上面的注释处的修改,首先如注释// 1
所示,我们要继承自std::enable_shared_from_this
,然后如注释// 2
所示,使用shared_from_this
获取到this
的智能指针。需要特别注意的是,我们必须保证Widget
的对象是通过智能指针构造出来的,如注释//3
标注所示。
但是
上面这段代码可以通过编译,但是在运行的时候会抛std::bad_weak_ptr
异常。上面这段代码逻辑上没有什么问题,真正的错误在于继承上,我们使用std::enable_shared_from_this
的时候,必须公有继承自它,而class
的默认的继承是私有继承。下面这段描述来自cppreference
Publicly inheriting from std::enable_shared_from_this
provides the type T with a member function shared_from_this
注意最前面的Public inheriting
。
为什么
实际上,enable_shared_from_this
类的内部,保存了一个weakptr
,这个weakptr
会在构造shared_ptr
的时候被设置,下面这段代码来自boost
:
1 | template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe ) |
如果我们使用的是公有继承,我们可以使用多态,把自己绑定到第一个模板函数的最后一个参数pe
上面,进而设置pe
内部的weakptr
。否则我们会fallback
到后面那个函数,导致enable_shared_from_this
内部的weakptr
未初始化成正确的值,最终我们调用shared_from_this
的时候会尝试从这个未初始化成正确的值的weakptr
构造一个shared_ptr
,导致std::bad_weak_ptr
异常的产生。