C++ scoped_ptr 和 unique_ptr對比分析
在 C++ 中,scoped_ptr 和 unique_ptr 都是用于管理獨占所有權(quán)的智能指針,但它們有一些重要的區(qū)別。
1. scoped_ptr
scoped_ptr 是 Boost 庫中的智能指針,提供了簡單的獨占所有權(quán)語義。
基本特性
#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不能被復(fù)制或移動 - 簡單輕量:幾乎沒有性能開銷
- 明確所有權(quán):清楚地表明指針擁有對象的所有權(quán)
boost::scoped_ptr<MyClass> ptr1(new MyClass); // boost::scoped_ptr<MyClass> ptr2 = ptr1; // 錯誤!不能拷貝 // boost::scoped_ptr<MyClass> ptr3(ptr1); // 錯誤!不能拷貝構(gòu)造
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() {
// 創(chuàng)建 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; // 自動釋放內(nèi)存
}3. 主要區(qū)別對比
| 特性 | scoped_ptr (Boost) | unique_ptr (C++11) |
|---|---|---|
| 標準支持 | 僅 Boost 庫 | C++11 標準庫 |
| 移動語義 | ? 不支持 | ? 支持 |
| 數(shù)組支持 | ? 需要 scoped_array | ? 內(nèi)置支持 |
| 自定義刪除器 | ? 不支持 | ? 支持 |
| 容器兼容性 | ? 不能放入容器 | ? 可以放入容器 |
| 性能 | 極輕量 | 輕量,功能更多 |
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);
// 移動所有權(quán)
std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
if (!ptr1) {
std::cout << "ptr1 現(xiàn)在為空\n";
}
if (ptr2) {
ptr2->show(); // 正常使用
}
// 從函數(shù)返回
auto ptr3 = createObject();
ptr3->show();
return 0;
}數(shù)組支持
// 管理動態(tài)數(shù)組
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());
}
// 文件會自動關(guān)閉
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. 所有權(quán)轉(zhuǎn)移模式
函數(shù)參數(shù)傳遞
void takeOwnership(std::unique_ptr<MyClass> ptr) {
std::cout << "函數(shù)獲得了對象的所有權(quán)\n";
ptr->show();
} // ptr 離開作用域,對象被銷毀
void borrowObject(MyClass* ptr) {
std::cout << "函數(shù)只是借用對象\n";
ptr->show();
} // 對象不會被銷毀
int main() {
auto ptr = std::make_unique<MyClass>(42);
// 轉(zhuǎn)移所有權(quán)
takeOwnership(std::move(ptr));
// 此時 ptr 為空
if (!ptr) {
std::cout << "ptr 已為空\n";
}
// 重新創(chuàng)建
ptr = std::make_unique<MyClass>(100);
// 只是借用,不轉(zhuǎn)移所有權(quán)
borrowObject(ptr.get());
// ptr 仍然有效
ptr->show();
return 0;
}6. 資源管理示例
對比原始指針
// 不好的做法 - 使用原始指針
void badExample() {
MyClass* rawPtr = new MyClass(42);
// ... 一些代碼 ...
if (someCondition) {
return; // 內(nèi)存泄漏!
}
// ... 更多代碼 ...
delete rawPtr; // 容易忘記
}
// 好的做法 - 使用 unique_ptr
void goodExample() {
auto ptr = std::make_unique<MyClass>(42);
// ... 一些代碼 ...
if (someCondition) {
return; // 自動釋放內(nèi)存!
}
// ... 更多代碼 ...
// 不需要手動刪除
}7. 實際應(yīng)用場景
工廠模式
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();
}總結(jié)
scoped_ptr:簡單的獨占所有權(quán),適用于不需要移動語義的簡單場景unique_ptr:功能豐富的獨占所有權(quán)指針,是現(xiàn)代 C++ 的首選
推薦使用 unique_ptr,因為:
- 它是 C++ 標準的一部分
- 支持移動語義,更靈活
- 有更好的容器兼容性
- 支持自定義刪除器和數(shù)組
在現(xiàn)代 C++ 開發(fā)中,應(yīng)該優(yōu)先使用 unique_ptr 來管理獨占所有權(quán)的資源,避免使用原始指針和 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: 是一個智能指針類,提供獨占所有權(quán)的內(nèi)存管理<Entity>: 模板參數(shù),指定指針指向的類型為Entityentity: 變量名
2.std::make_unique<Entity>()
std::make_unique: C++14 引入的工廠函數(shù),用于創(chuàng)建unique_ptr<Entity>(): 在堆上動態(tài)分配一個Entity對象,調(diào)用其默認構(gòu)造函數(shù)
等效的傳統(tǒng)寫法
// 傳統(tǒng)方式(不推薦) std::unique_ptr<Entity> entity(new Entity()); // 或者 Entity* raw_ptr = new Entity(); std::unique_ptr<Entity> entity(raw_ptr);
優(yōu)勢
- 異常安全:
make_unique提供強異常安全保證 - 代碼簡潔: 一行完成內(nèi)存分配和智能指針構(gòu)造
- 避免內(nèi)存泄漏: 自動管理內(nèi)存生命周期
- 獨占所有權(quán): 防止多個指針指向同一對象
內(nèi)存管理
當 entity 離開作用域時,會自動調(diào)用析構(gòu)函數(shù)并釋放內(nèi)存:
{
std::unique_ptr<Entity> entity = std::make_unique<Entity>();
// 使用 entity...
} // 此處 entity 自動銷毀,Entity 對象被刪除
這種寫法是現(xiàn)代 C++ 中管理動態(tài)內(nèi)存的推薦方式。
unique_ptr(const Myt&)= delete; Myt& operator=(const Myt&)= delete; // 這兩行代碼使用了 C++ 的 刪除函數(shù)(deleted functions) 特性來顯式禁止拷貝操作。
第一行:禁止拷貝構(gòu)造函數(shù)
unique_ptr(const Myt&) = delete;
unique_ptr(const Myt&): 拷貝構(gòu)造函數(shù)的聲明Myt通常是模板參數(shù),代表unique_ptr本身的類型- 參數(shù)
const Myt&表示接受一個常量引用到同類型的unique_ptr
= delete: 將該函數(shù)標記為"已刪除"- 編譯時如果嘗試調(diào)用此函數(shù)會導(dǎo)致錯誤
- 比將其聲明為
private更直接明確
第二行:禁止拷貝賦值運算符
Myt& operator=(const Myt&) = delete;
Myt& operator=(const Myt&): 拷貝賦值運算符的聲明- 返回
Myt&(引用到當前對象),支持鏈式賦值 - 參數(shù)
const Myt&表示接受一個常量引用到同類型的unique_ptr
- 返回
= delete: 同樣標記為已刪除
設(shè)計意圖
為什么unique_ptr要禁止拷貝?
獨占所有權(quán)語義:
std::unique_ptr<Entity> ptr1 = std::make_unique<Entity>(); std::unique_ptr<Entity> ptr2 = ptr1; // 編譯錯誤!
避免資源重復(fù)釋放:
- 如果允許拷貝,兩個
unique_ptr可能指向同一對象 - 析構(gòu)時會導(dǎo)致雙重釋放(double free)
- 如果允許拷貝,兩個
明確資源轉(zhuǎn)移:
- 使用
std::move()進行所有權(quán)轉(zhuǎn)移
std::unique_ptr<Entity> ptr2 = std::move(ptr1); // 允許:轉(zhuǎn)移所有權(quán)
- 使用
對比其他智能指針
std::shared_ptr: 允許拷貝(引用計數(shù))std::weak_ptr: 允許拷貝(不增加引用計數(shù))std::unique_ptr: 禁止拷貝(獨占所有權(quán))
現(xiàn)代 C++ 最佳實踐
使用 = delete 比傳統(tǒng)的 private 方法更清晰:
// 傳統(tǒng)方法(C++98/03)
class MyClass {
private:
MyClass(const MyClass&); // 不實現(xiàn)
MyClass& operator=(const MyClass&); // 不實現(xiàn)
};
// 現(xiàn)代方法(C++11+)
class MyClass {
public:
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
};這種設(shè)計確保了 unique_ptr 的獨占所有權(quán)語義,防止意外的資源管理錯誤。
??優(yōu)先選擇使用unique_ptr,其次是shared_ptr??
這種說法源于 C++ 核心指南和現(xiàn)代 C++ 最佳實踐,主要有以下幾個重要原因:
1. 所有權(quán)語義更明確
unique_ptr- 獨占所有權(quán)
std::unique_ptr<Entity> createEntity() {
return std::make_unique<Entity>(); // 明確:所有權(quán)被轉(zhuǎn)移出去
}
auto entity = createEntity(); // 明確:我是唯一所有者shared_ptr- 共享所有權(quán)(可能模糊)
std::shared_ptr<Entity> createEntity() {
return std::make_shared<Entity>(); // 模糊:誰擁有這個對象?
}
auto entity = createEntity(); // 可能有多個共享所有者2. 性能優(yōu)勢
內(nèi)存和性能開銷對比
// unique_ptr - 零開銷抽象 std::unique_ptr<Entity> uptr; // 大?。和ǔ?個指針(8字節(jié)) // 開銷:無額外分配,析構(gòu)時直接delete // shared_ptr - 有顯著開銷 std::shared_ptr<Entity> sptr; // 大小:通常2個指針(16字節(jié)) // 開銷:控制塊分配、引用計數(shù)原子操作
3. 避免意外的生命周期延長
shared_ptr的陷阱
void process(const std::shared_ptr<Entity>& entity) {
// 如果內(nèi)部存儲了 shared_ptr,會意外延長生命周期
background_tasks.store(entity); // 對象生命周期被意外延長!
}
auto entity = std::make_shared<Entity>();
process(entity); // 可能造成生命周期問題unique_ptr更安全
void process(std::unique_ptr<Entity> entity) {
// 明確:所有權(quán)被轉(zhuǎn)移,調(diào)用者失去所有權(quán)
// 不會意外共享所有權(quán)
}
4. 避免循環(huán)引用問題
shared_ptr的循環(huán)引用
struct Node {
std::shared_ptr<Node> next;
// 可能形成循環(huán)引用,導(dǎo)致內(nèi)存泄漏
};
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1; // 循環(huán)引用!unique_ptr無此問題
struct Node {
std::unique_ptr<Node> next; // 明確的所有權(quán)鏈
// 不可能形成循環(huán)引用
};
5. 代碼可維護性
unique_ptr使依賴關(guān)系清晰
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
雖然優(yōu)先選擇 unique_ptr,但 shared_ptr 在以下情況是合適的:
// 1. 真正的共享所有權(quán)
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);總結(jié)
設(shè)計原則:
- 默認使用
unique_ptr- 除非明確需要共享所有權(quán) - 使用
shared_ptr- 當確實需要多個所有者時 - 使用
weak_ptr- 打破循環(huán)引用或觀察而不擁有
這種選擇策略能帶來更好的性能、更清晰的所有權(quán)語義和更少的資源管理錯誤。
(注:文檔部分內(nèi)容可能由 AI 生成)
到此這篇關(guān)于C++ scoped_ptr 和 unique_ptr對比分析的文章就介紹到這了,更多相關(guān)C++ scoped_ptr 和 unique_ptr內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt重寫QStackedWidget模擬實現(xiàn)home界面滑動效果
這篇文章主要為大家詳細介紹了Qt如何通過重寫QStackedWidget模擬實現(xiàn)home界面滑動效果,文中的實現(xiàn)過程講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2022-11-11
淺談C++中的構(gòu)造函數(shù)分類及調(diào)用規(guī)則
這篇文章主要介紹了C++中的構(gòu)造函數(shù)分類及調(diào)用規(guī)則,文中根據(jù)參數(shù)寫出了幾種不同類型的構(gòu)造函數(shù)并解釋了如何調(diào)用,需要的朋友可以參考下2016-03-03
C語言左旋轉(zhuǎn)字符串與翻轉(zhuǎn)字符串中單詞順序的方法
這篇文章主要介紹了C語言左旋轉(zhuǎn)字符串與翻轉(zhuǎn)字符串中單詞順序的方法,給出了相關(guān)的兩道算法題目作為例子,需要的朋友可以參考下2016-02-02

