使用shared_ptr从原始指针构造致程序异常退出的原因及构造函数3用例咨询
问题描述
我正在试验C++14的shared_ptr以探究其功能,在Visual Studio 2022控制台程序中,通过已有shared_ptr的原始指针构造新shared_ptr时,代码可编译运行,但main()退出状态非0。无调试器运行时显示退出码-1073741819,调试时弹出未处理异常,添加try-catch也无法捕获。请指出构造操作中的问题,同时说明shared_ptr接受原始指针的构造函数的适用场景。
相关代码
#include <iostream> #include <string> #include <memory> #include <exception> class Dog { public: Dog(std::string name) : m_name(name) { std::cout << "Dog " << m_name << " created\n"; } void bark() { std::cout << m_name << " wooof\n"; } ~Dog() { std::cout << m_name << " removed\n"; } private: std::string m_name; }; int main() { std::shared_ptr<Dog> dog_sp(new Dog("Fido")); int use_count = dog_sp.use_count(); printf("use_count1=%d\n", use_count); std::shared_ptr<Dog> dog_sp2(dog_sp); use_count = dog_sp2.use_count(); printf("use_count2=%d\n", use_count); Dog* dog_ptr = dog_sp.get(); dog_ptr->bark(); try { std::shared_ptr<Dog> dog_sp3(dog_ptr); use_count = dog_sp3.use_count(); dog_sp3->bark(); printf("use_count3=%d\n", use_count); } catch (std::exception& ex) { std::cout << "Exception: " << ex.what() << std::endl; } catch (const std::string& ex) { std::cout << "Exception: " << ex << std::endl; } catch (...) { std::cout << "Exception" << std::endl; } return 0; }
控制台输出
Dog Fido created use_count1=1 use_count2=2 Fido wooof Fido wooof use_count3=1 Fido removed
问题分析与解答
构造操作的核心问题
用dog_sp.get()获取的原始指针dog_ptr构造dog_sp3,会导致同一个原始指针被两个独立的shared_ptr实例管理——它们各自维护自己的引用计数,完全不知道对方的存在。
程序退出时的执行流程:
- 首先
dog_sp3销毁,其引用计数降到0,调用Dog的析构函数释放Fido的内存; - 随后
dog_sp和dog_sp2销毁,它们的引用计数也降到0,会再次尝试释放已经被释放的内存,触发*双重释放(double free)*的未定义行为。
这种底层内存错误不属于C++标准异常体系,所以try-catch无法捕获,调试器会弹出未处理异常,无调试运行时返回错误码-1073741819(对应Windows的STATUS_ACCESS_VIOLATION,即访问违规)。
从控制台输出也能看出异常征兆:Fido removed只打印了一次,但实际上两次析构尝试中,第二次才触发崩溃。
shared_ptr接受原始指针的构造函数适用场景
这个构造函数(explicit shared_ptr(T* p))的正确用法仅限以下场景:
- 管理新分配的原始指针:即你直接用
new创建的对象,且该指针还没有被任何其他智能指针接管。比如std::shared_ptr<Dog> dog_sp(new Dog("Fido"))就是正确用法,此时shared_ptr成为该原始指针的唯一所有者,负责后续的内存释放。 - 绝对不能用它构造已经被其他
shared_ptr管理的原始指针,否则必然引发双重释放。如果要共享对象所有权,应该直接使用shared_ptr的拷贝构造或赋值操作(比如std::shared_ptr<Dog> dog_sp2(dog_sp)),这样才能让所有共享实例的引用计数正确递增,确保内存只被释放一次。
内容的提问来源于stack exchange,提问作者Artur Brodsky
相关产品推荐
相关产品推荐

