关于 shared_ptr 创建不可删除对象(私有析构函数)的指针时,只能调用移动函数 | C2664...(std::shared_ptr<A> &&)...“std::nullptr_t”

正常情况下,基类指针指向派生类对象时,使用智能指针。

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫RT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值