含std::unique_ptr的类B无法嵌套指向?编译错误排查与解决
问题:含std::unique_ptr的对象无法嵌套持有同类实例?
我编写了如下最小可复现示例,发现包含std::unique_ptr的对象B似乎无法持有指向另一个B实例的std::unique_ptr:
#include <iostream> #include <memory> struct C { virtual void say_name() = 0; }; struct A : public C { int value; A(int value) : value(value) {} void say_name() override { std::cout << "A" << value << std::endl; } }; struct B : public C { std::unique_ptr<C> ptr; B(std::unique_ptr<C> ptr) : ptr(std::move(ptr)) {} void say_name() override { std::cout << "B" << std::endl; } }; int main() { A a(5); B b(std::make_unique<A>(10)); // 这里创建另一个B会报错 B b2(std::make_unique<B>(b)); }
预期b持有A,b2持有b,但创建b2时出现错误,提示调用了被隐式删除的B的拷贝构造函数。完整错误信息如下:
In template: call to implicitly-deleted copy constructor of 'B'clang(ovl_deleted_special_init) unique_ptr.h(767, 30): Error occurred here main.cpp(33, 13): In instantiation of function template specialization 'std::make_unique<B, B &, 0>' requested here main.cpp(24, 22): Copy constructor of 'B' is implicitly deleted because field 'ptr' has a deleted copy constructor unique_ptr.h(221, 55): Copy constructor is implicitly deleted because 'unique_ptr<C>' has a user-declared move constructor
错误原因
std::unique_ptr是独占所有权的智能指针,它的拷贝构造函数被显式删除——毕竟独占所有权的指针不能被复制,否则会出现多个指针管理同一块内存的矛盾。B类包含std::unique_ptr<C>成员,当类成员包含不可拷贝的类型时,编译器会自动删除该类的拷贝构造函数。- 创建
b2时,std::make_unique<B>(b)尝试将b作为参数传递,但B的构造函数需要std::unique_ptr<C>,此时编译器会尝试拷贝b来构造内部的B实例,而B的拷贝构造函数已被删除,因此报错。
修复方案
根据你的需求选择对应的方案:
1. 转移所有权(让b2独占原b的资源)
如果希望b2完全接手b持有的资源,之后b不再使用,可以用std::move转移所有权:
int main() { A a(5); B b(std::make_unique<A>(10)); // 用std::move转移b的所有权给新创建的B实例 B b2(std::make_unique<B>(std::move(b))); // 注意:此时b的ptr已被转移,不能再使用b }
std::move(b)会触发B的移动构造函数(编译器会自动生成,因为unique_ptr支持移动),将b的资源转移给新的B对象,再被b2的ptr持有。
2. 共享所有权(让b2和b共享同一资源)
如果需要多个B实例共享同一内部对象的所有权,将std::unique_ptr替换为std::shared_ptr(共享所有权的智能指针,支持拷贝):
struct B : public C { std::shared_ptr<C> ptr; B(std::shared_ptr<C> ptr) : ptr(std::move(ptr)) {} void say_name() override { std::cout << "B" << std::endl; } }; int main() { A a(5); auto b_shared = std::make_shared<B>(std::make_shared<A>(10)); B b2(b_shared); // 此时b_shared和b2.ptr都指向同一个B对象,该对象持有A实例 }
std::shared_ptr通过引用计数管理内存,当最后一个指向对象的shared_ptr销毁时,内存才会被释放。
3. 持有对象引用(不转移/共享所有权)
如果只是想让b2持有b的引用,且能保证b的生命周期长于b2,可以改用裸指针(但裸指针需手动管理内存,风险较高):
struct B : public C { C* ptr; B(C* ptr) : ptr(ptr) {} void say_name() override { std::cout << "B" << std::endl; } // 注意:此处不负责释放ptr的内存,所有权在外部 }; int main() { A a(5); // 手动管理A的内存,释放unique_ptr的所有权 B b(std::make_unique<A>(10).release()); B b2(&b); b.say_name(); b2.say_name(); // 手动释放A的内存 delete b.ptr; }
这种方式需严格控制对象生命周期,避免出现悬空指针,非必要不推荐。
内容的提问来源于stack exchange,提问作者Featherball
相关产品推荐
相关产品推荐

