在 c++ 中,scoped_ptr 和 unique_ptr 都是用于管理独占所有权的智能指针,但它们有一些重要的区别。
1. scoped_ptr
scoped_ptr 是 boost 库中的智能指针,提供了简单的独占所有权语义。
基本特性
#include <boost/scoped_ptr.hpp>
class myclass {
public:
myclass() { std::cout << "myclass constructed\n"; }
~myclass() { std::cout << "myclass destroyed\n"; }
void dosomething() { std::cout << "doing something\n"; }
};
int main() {
boost::scoped_ptr<myclass> ptr(new myclass);
ptr->dosomething();
// 当 ptr 离开作用域时,会自动删除管理的对象
return 0;
}主要特点
- 不可拷贝和移动:
scoped_ptr不能被复制或移动 - 简单轻量:几乎没有性能开销
- 明确所有权:清楚地表明指针拥有对象的所有权
boost::scoped_ptr<myclass> ptr1(new myclass); // boost::scoped_ptr<myclass> ptr2 = ptr1; // 错误!不能拷贝 // boost::scoped_ptr<myclass> ptr3(ptr1); // 错误!不能拷贝构造
2. unique_ptr
unique_ptr 是 c++11 标准引入的智能指针,提供了更丰富的功能。
基本用法
#include <memory>
class myclass {
public:
myclass(int value) : data(value) {
std::cout << "myclass constructed with " << value << "\n";
}
~myclass() { std::cout << "myclass destroyed\n"; }
void show() { std::cout << "data: " << data << "\n"; }
private:
int data;
};
int main() {
// 创建 unique_ptr
std::unique_ptr<myclass> ptr1(new myclass(42));
// 使用 make_unique (c++14)
auto ptr2 = std::make_unique<myclass>(100);
ptr1->show();
ptr2->show();
return 0; // 自动释放内存
}3. 主要区别对比
| 特性 | scoped_ptr (boost) | unique_ptr (c++11) |
|---|---|---|
| 标准支持 | 仅 boost 库 | c++11 标准库 |
| 移动语义 | ❌ 不支持 | ✅ 支持 |
| 数组支持 | ❌ 需要 scoped_array | ✅ 内置支持 |
| 自定义删除器 | ❌ 不支持 | ✅ 支持 |
| 容器兼容性 | ❌ 不能放入容器 | ✅ 可以放入容器 |
| 性能 | 极轻量 | 轻量,功能更多 |
4. unique_ptr 的高级特性
移动语义
std::unique_ptr<myclass> createobject() {
return std::make_unique<myclass>(999);
}
int main() {
std::unique_ptr<myclass> ptr1 = std::make_unique<myclass>(42);
// 移动所有权
std::unique_ptr<myclass> ptr2 = std::move(ptr1);
if (!ptr1) {
std::cout << "ptr1 现在为空\n";
}
if (ptr2) {
ptr2->show(); // 正常使用
}
// 从函数返回
auto ptr3 = createobject();
ptr3->show();
return 0;
}数组支持
// 管理动态数组
std::unique_ptr<int[]> arrptr(new int[10]);
for (int i = 0; i < 10; ++i) {
arrptr[i] = i * i; // 可以直接使用下标
}
// 或者使用 make_unique (c++14)
auto arrptr2 = std::make_unique<int[]>(5);自定义删除器
// 文件指针的自定义删除器
struct filedeleter {
void operator()(file* file) {
if (file) {
fclose(file);
std::cout << "file closed\n";
}
}
};
int main() {
std::unique_ptr<file, filedeleter> fileptr(fopen("test.txt", "w"));
if (fileptr) {
fputs("hello, world!", fileptr.get());
}
// 文件会自动关闭
return 0;
}在容器中使用
#include <vector>
#include <memory>
int main() {
std::vector<std::unique_ptr<myclass>> objects;
// 添加对象到向量
objects.push_back(std::make_unique<myclass>(1));
objects.push_back(std::make_unique<myclass>(2));
objects.push_back(std::make_unique<myclass>(3));
// 使用对象
for (const auto& obj : objects) {
obj->show();
}
return 0;
}5. 所有权转移模式
函数参数传递
void takeownership(std::unique_ptr<myclass> ptr) {
std::cout << "函数获得了对象的所有权\n";
ptr->show();
} // ptr 离开作用域,对象被销毁
void borrowobject(myclass* ptr) {
std::cout << "函数只是借用对象\n";
ptr->show();
} // 对象不会被销毁
int main() {
auto ptr = std::make_unique<myclass>(42);
// 转移所有权
takeownership(std::move(ptr));
// 此时 ptr 为空
if (!ptr) {
std::cout << "ptr 已为空\n";
}
// 重新创建
ptr = std::make_unique<myclass>(100);
// 只是借用,不转移所有权
borrowobject(ptr.get());
// ptr 仍然有效
ptr->show();
return 0;
}6. 资源管理示例
对比原始指针
// 不好的做法 - 使用原始指针
void badexample() {
myclass* rawptr = new myclass(42);
// ... 一些代码 ...
if (somecondition) {
return; // 内存泄漏!
}
// ... 更多代码 ...
delete rawptr; // 容易忘记
}
// 好的做法 - 使用 unique_ptr
void goodexample() {
auto ptr = std::make_unique<myclass>(42);
// ... 一些代码 ...
if (somecondition) {
return; // 自动释放内存!
}
// ... 更多代码 ...
// 不需要手动删除
}7. 实际应用场景
工厂模式
class product {
public:
virtual ~product() = default;
virtual void use() = 0;
};
class concreteproduct : public product {
public:
void use() override {
std::cout << "using concreteproduct\n";
}
};
std::unique_ptr<product> createproduct() {
return std::make_unique<concreteproduct>();
}
int main() {
auto product = createproduct();
product->use();
return 0;
}pimpl 惯用法
// myclass.h
class myclass {
public:
myclass();
~myclass(); // 需要显式定义,因为 unique_ptr 需要完整类型
void publicmethod();
private:
class impl;
std::unique_ptr<impl> pimpl;
};
// myclass.cpp
class myclass::impl {
public:
void privatemethod() {
std::cout << "private method called\n";
}
int data = 42;
};
myclass::myclass() : pimpl(std::make_unique<impl>()) {}
myclass::~myclass() = default; // 需要看到 impl 的完整定义
void myclass::publicmethod() {
pimpl->privatemethod();
}总结
scoped_ptr:简单的独占所有权,适用于不需要移动语义的简单场景unique_ptr:功能丰富的独占所有权指针,是现代 c++ 的首选
推荐使用 unique_ptr,因为:
- 它是 c++ 标准的一部分
- 支持移动语义,更灵活
- 有更好的容器兼容性
- 支持自定义删除器和数组
在现代 c++ 开发中,应该优先使用 unique_ptr 来管理独占所有权的资源,避免使用原始指针和 scoped_ptr。
std::unique ptr<entity> entity = std::make unique<entity>();
代码解析
std::unique_ptr<entity> entity = std::make_unique<entity>();
1.std::unique_ptr<entity>
std::unique_ptr: 是一个智能指针类,提供独占所有权的内存管理<entity>: 模板参数,指定指针指向的类型为entityentity: 变量名
2.std::make_unique<entity>()
std::make_unique: c++14 引入的工厂函数,用于创建unique_ptr<entity>(): 在堆上动态分配一个entity对象,调用其默认构造函数
等效的传统写法
// 传统方式(不推荐) std::unique_ptr<entity> entity(new entity()); // 或者 entity* raw_ptr = new entity(); std::unique_ptr<entity> entity(raw_ptr);
优势
- 异常安全:
make_unique提供强异常安全保证 - 代码简洁: 一行完成内存分配和智能指针构造
- 避免内存泄漏: 自动管理内存生命周期
- 独占所有权: 防止多个指针指向同一对象
内存管理
当 entity 离开作用域时,会自动调用析构函数并释放内存:
{
std::unique_ptr<entity> entity = std::make_unique<entity>();
// 使用 entity...
} // 此处 entity 自动销毁,entity 对象被删除
这种写法是现代 c++ 中管理动态内存的推荐方式。
unique_ptr(const myt&)= delete; myt& operator=(const myt&)= delete; // 这两行代码使用了 c++ 的 删除函数(deleted functions) 特性来显式禁止拷贝操作。
第一行:禁止拷贝构造函数
unique_ptr(const myt&) = delete;
unique_ptr(const myt&): 拷贝构造函数的声明myt通常是模板参数,代表unique_ptr本身的类型- 参数
const myt&表示接受一个常量引用到同类型的unique_ptr
= delete: 将该函数标记为"已删除"- 编译时如果尝试调用此函数会导致错误
- 比将其声明为
private更直接明确
第二行:禁止拷贝赋值运算符
myt& operator=(const myt&) = delete;
myt& operator=(const myt&): 拷贝赋值运算符的声明- 返回
myt&(引用到当前对象),支持链式赋值 - 参数
const myt&表示接受一个常量引用到同类型的unique_ptr
- 返回
= delete: 同样标记为已删除
设计意图
为什么unique_ptr要禁止拷贝?
独占所有权语义:
std::unique_ptr<entity> ptr1 = std::make_unique<entity>(); std::unique_ptr<entity> ptr2 = ptr1; // 编译错误!
避免资源重复释放:
- 如果允许拷贝,两个
unique_ptr可能指向同一对象 - 析构时会导致双重释放(double free)
- 如果允许拷贝,两个
明确资源转移:
- 使用
std::move()进行所有权转移
std::unique_ptr<entity> ptr2 = std::move(ptr1); // 允许:转移所有权
- 使用
对比其他智能指针
std::shared_ptr: 允许拷贝(引用计数)std::weak_ptr: 允许拷贝(不增加引用计数)std::unique_ptr: 禁止拷贝(独占所有权)
现代 c++ 最佳实践
使用 = delete 比传统的 private 方法更清晰:
// 传统方法(c++98/03)
class myclass {
private:
myclass(const myclass&); // 不实现
myclass& operator=(const myclass&); // 不实现
};
// 现代方法(c++11+)
class myclass {
public:
myclass(const myclass&) = delete;
myclass& operator=(const myclass&) = delete;
};这种设计确保了 unique_ptr 的独占所有权语义,防止意外的资源管理错误。
??优先选择使用unique_ptr,其次是shared_ptr??
这种说法源于 c++ 核心指南和现代 c++ 最佳实践,主要有以下几个重要原因:
1. 所有权语义更明确
unique_ptr- 独占所有权
std::unique_ptr<entity> createentity() {
return std::make_unique<entity>(); // 明确:所有权被转移出去
}
auto entity = createentity(); // 明确:我是唯一所有者shared_ptr- 共享所有权(可能模糊)
std::shared_ptr<entity> createentity() {
return std::make_shared<entity>(); // 模糊:谁拥有这个对象?
}
auto entity = createentity(); // 可能有多个共享所有者2. 性能优势
内存和性能开销对比
// unique_ptr - 零开销抽象 std::unique_ptr<entity> uptr; // 大小:通常1个指针(8字节) // 开销:无额外分配,析构时直接delete // shared_ptr - 有显著开销 std::shared_ptr<entity> sptr; // 大小:通常2个指针(16字节) // 开销:控制块分配、引用计数原子操作
3. 避免意外的生命周期延长
shared_ptr的陷阱
void process(const std::shared_ptr<entity>& entity) {
// 如果内部存储了 shared_ptr,会意外延长生命周期
background_tasks.store(entity); // 对象生命周期被意外延长!
}
auto entity = std::make_shared<entity>();
process(entity); // 可能造成生命周期问题unique_ptr更安全
void process(std::unique_ptr<entity> entity) {
// 明确:所有权被转移,调用者失去所有权
// 不会意外共享所有权
}
4. 避免循环引用问题
shared_ptr的循环引用
struct node {
std::shared_ptr<node> next;
// 可能形成循环引用,导致内存泄漏
};
auto node1 = std::make_shared<node>();
auto node2 = std::make_shared<node>();
node1->next = node2;
node2->next = node1; // 循环引用!unique_ptr无此问题
struct node {
std::unique_ptr<node> next; // 明确的所有权链
// 不可能形成循环引用
};
5. 代码可维护性
unique_ptr使依赖关系清晰
class game {
std::unique_ptr<renderer> renderer_;
std::unique_ptr<physicsengine> physics_;
public:
game(std::unique_ptr<renderer> renderer,
std::unique_ptr<physicsengine> physics)
: renderer_(std::move(renderer))
, physics_(std::move(physics))
{}
// 明确:game 独占拥有这些组件
};6. 何时使用shared_ptr
虽然优先选择 unique_ptr,但 shared_ptr 在以下情况是合适的:
// 1. 真正的共享所有权
class texturecache {
std::unordered_map<std::string, std::shared_ptr<texture>> cache_;
public:
std::shared_ptr<texture> gettexture(const std::string& name) {
// 多个地方可能同时使用同一个纹理
return cache_[name];
}
};
// 2. 需要弱引用的情况
std::shared_ptr<connection> connection = createconnection();
std::weak_ptr<connection> weak_conn = connection; // 观察而不拥有
// 3. 与第三方api集成
void registercallback(std::shared_ptr<handler> handler);总结
设计原则:
- 默认使用
unique_ptr- 除非明确需要共享所有权 - 使用
shared_ptr- 当确实需要多个所有者时 - 使用
weak_ptr- 打破循环引用或观察而不拥有
这种选择策略能带来更好的性能、更清晰的所有权语义和更少的资源管理错误。
(注:文档部分内容可能由 ai 生成)
到此这篇关于c++ scoped_ptr 和 unique_ptr对比分析的文章就介绍到这了,更多相关c++ scoped_ptr 和 unique_ptr内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论