C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ scoped_ptr 和 unique_ptr

C++ scoped_ptr 和 unique_ptr对比分析

作者:赫尔·普莱蒂科萨·帕塔

本文介绍了C++中的`scoped_ptr`和`unique_ptr`,详细比较了它们的特性、使用场景以及现代C++推荐的使用`unique_ptr`来管理独占所有权的资源,感兴趣的朋友跟随小编一起看看吧

在 C++ 中,scoped_ptrunique_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;
}

主要特点

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();
}

总结

推荐使用 unique_ptr,因为:

  1. 它是 C++ 标准的一部分
  2. 支持移动语义,更灵活
  3. 有更好的容器兼容性
  4. 支持自定义删除器和数组

在现代 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>

2.std::make_unique<Entity>()

等效的传统写法

// 传统方式(不推荐)
std::unique_ptr<Entity> entity(new Entity());
// 或者
Entity* raw_ptr = new Entity();
std::unique_ptr<Entity> entity(raw_ptr);

优势

  1. 异常安全: make_unique 提供强异常安全保证
  2. 代码简洁: 一行完成内存分配和智能指针构造
  3. 避免内存泄漏: 自动管理内存生命周期
  4. 独占所有权: 防止多个指针指向同一对象

内存管理

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;

第二行:禁止拷贝赋值运算符

Myt& operator=(const Myt&) = delete;

设计意图

为什么unique_ptr要禁止拷贝?

  1. 独占所有权语义:

    std::unique_ptr<Entity> ptr1 = std::make_unique<Entity>();
    std::unique_ptr<Entity> ptr2 = ptr1; // 编译错误!
  2. 避免资源重复释放:

    • 如果允许拷贝,两个 unique_ptr 可能指向同一对象
    • 析构时会导致双重释放(double free)
  3. 明确资源转移:

    • 使用 std::move() 进行所有权转移
    std::unique_ptr<Entity> ptr2 = std::move(ptr1); // 允许:转移所有权
    

对比其他智能指针

现代 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);

总结

设计原则

这种选择策略能带来更好的性能、更清晰的所有权语义和更少的资源管理错误。

(注:文档部分内容可能由 AI 生成)

到此这篇关于C++ scoped_ptr 和 unique_ptr对比分析的文章就介绍到这了,更多相关C++ scoped_ptr 和 unique_ptr内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文