正常情况下,基类指针指向派生类对象时,使用智能指针。
class A
{
public:
virtual ~A() { std::cout << "destructed: ~A() " << std::endl; }
};
class B :public A
{
public:
~B() { std::cout << "destructed: ~B() " << std::endl; }
};
int main()
{
std::unique_ptr<A> upa(new B()); // ok
std::shared_ptr<A> spa(new B()); // No
// or
std::shared_ptr<A> spa2 = std::make_shared<B>(); // ok
}
如果将派生类的析构函数设置为私有。shared_ptr无法正常创建。
注:在基类是虚函数的情况下,派生类中函数在私有状态不影响最终的调用。
class A
{
public:
virtual ~A() { std::cout << "destructed: ~A() " << std::endl; }
};
class B :public A
{
~B() { std::cout << "destructed: ~B() " << std::endl; }
};
int main()
{
// 基类指针指向派生类对象,派生类对象的析构函数不可访问。
// 注:类成员访问权限只限制编译时期的语法检查,运行时,虚函数通过虚表查询调用,
// 即,B中声明为私有的虚函数,可以通过虚表指针访问。
// 如B::~B() 函数记录在虚表中,通过虚表指针调用
std::unique_ptr<A> upa(new B()); // ok
//std::shared_ptr<A> spa(new B()); // No
/*
~A() 为私有(隐式删除)时,不可直接创建
*/
// 解决方案一:把对应析构设置为公有
;;;;;
// 解决方案二:先使用普通指针接收子类对象,在使用shared_ptr创建
A* p = new B();
std::shared_ptr<A> spb(p);
}
当我们使用 std::shared_ptr<A> spa(new B());
创建对象时,编译器报错如下:
StackOverflow上查到,有回复如下:(以下是翻译后的截图)
图中的函数:
template<class _Ux,
enable_if_t<conjunction_v<conditional_t<is_array_v<_Ty>, _Can_array_delete<_Ux>, _Can_scalar_delete<_Ux>>,
_SP_convertible<_Ux, _Ty>>, int> = 0>
explicit shared_ptr(_Ux * _Px)
由此,可知当编译器检查到某个类的析构函数不可访问时,即无法通过正常途径生成对象,比如 B b;
这样在栈上申请对象是不允许的,因为语法检查到析构不可调用。
而 B* b = new B();
这样的语法是可以的,因为我们虽无法直接调用析构,但我们可以通过其他方式销毁空间,比如delete b;
因此,把析构设置为私有的作用可以不让对象在栈上生成。
而shared_ptr 对于此种不能调用析构的对象,是直接禁用了构造函数,让其无法生成新的对象,只能使用移动函数(移动构造、移动赋值)使用已经生成的对象。
一些常用的类成员访问符用法:
- 私有构造函数的作用:不可产生实例,除非在类中定义一个静态实例。如单例模式。
- 保护构造函数的作用:当前类不生成实例,继承自该类的子类可以生成实例。
- 私有new运算符作用:不可在堆区(不可动态生成对象)申请对象。
void* operator new(size_t) = delete;
- 私有析构函数的作用:令对象只能在堆上生成,即用new方法。原理是C++是一个静态绑定语言,在编译过程中,所有的非虚函数调用都必须分析完成(虚函数也要检查可访问性)。因此,当在栈上生成对象时,对象会自动析构,即析构函数必须可以访问。而在堆上生成对象时,析构步骤由程序员控制,不一定需要析构函数。同时,此生成对象不能直接delete删除,析构过程还需要一个专门的成员函数。
总结:
以下只是推测:
shared_ptr有检查对象是否可删除,即析构是否可用。判断当前类不可析构时,(当析构不可用时,按道理讲就不应该再创建新对象,而对于已存在的对象我们猜想创建着有着特殊的针对其销毁的规则,不需要我们(shared_ptr)关心,而当使用这些已存在的实例构建新对象时,我们认为不应该在产生新的对象,因此我们使用移动的方式 ‘引用’ 原本的对象)。则认为当前只有一份实例,shared_ptr只能调用移动构造或移动赋值实现资源的转移。
而unique_ptr在本身是独占型指针,在假设该类无法析构的同时,通过已有的实例调用构造、也不会生成新的实例。因此,对于不可删除类型(这里指的是析构函数设置为私有的情况),unique_ptr可以照常使用。
class A
{
public:
virtual ~A() { std::cout << "destructed: ~A() " << std::endl; }
};
class B :public A
{
public:
B() {}
private:
~B() { std::cout << "destructed: ~B() " << std::endl; }
};
int main()
{
std::unique_ptr<A> upa(new B()); // ok ,创建 & 释放 √
A* pab = new B(); // ok,可以动态创建对象,可以delete释放
B* pb = new B(); // ok,可以动态创建对象,不可以delete释放
delete pab;
//delete pb; // error ,析构不可访问
delete (A*)pb; // 通过强转为父类型指针,父指针通过虚表调用B的析构
std::shared_ptr<A> psa((A*)new B()); // 通过强转实现shared_ptr的创建
return 0;
}