C++ scoped_ptr 和 unique_ptr对比分析

2025-11-26 19:50
文章标签 ptr c++ scoped unique 对比 分析

本文主要是介绍C++ scoped_ptr 和 unique_ptr对比分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《C++scoped_ptr和unique_ptr对比分析》本文介绍了C++中的`scoped_ptr`和`unique_ptr`,详细比较了它们的特性、使用场景以及现代C++推荐的使用`uni...

在 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;
}

主要特点

  • 不可拷贝和移动scopiiHtJHHed_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() {
    androidstd::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() {
    pIpythonmpl->privateMethod();
}

总结

  • scoped_ptr:简单的独占所有权,适用于不需要移动语义的简单场景
  • unique_ptr:功能丰富的独占所有权指针,是现代 C++ 的首选

推荐使用 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>

  • std::unique_ptr: 是一个智能指针类,提供独占所有权的内存管理
  • <Entity>: 模板参数,指定指针指向的类型为 Entity
  • entity: 变量名

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

优势

  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;
// 这两行android代码使用了 C++ 的 删除函数(deleted functions) 特性来显式禁止拷贝操作。

第一行:禁止拷贝构造函数

unique_ptr(const Myt&) = delete;
  • unique_ptr(const phpMyt&): 拷贝构造函数的声明
    • 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要禁止拷贝?

  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); // 允许:转移所有权
    

对比其他智能指针

  • 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内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于C++ scoped_ptr 和 unique_ptr对比分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156288

相关文章

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias

C++11中的包装器实战案例

《C++11中的包装器实战案例》本文给大家介绍C++11中的包装器实战案例,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录引言1.std::function1.1.什么是std::function1.2.核心用法1.2.1.包装普通函数1.2.

C++多线程开发环境配置方法

《C++多线程开发环境配置方法》文章详细介绍了如何在Windows上安装MinGW-w64和VSCode,并配置环境变量和编译任务,使用VSCode创建一个C++多线程测试项目,并通过配置tasks.... 目录下载安装 MinGW-w64下载安装VS code创建测试项目配置编译任务创建 tasks.js

Nginx内置变量应用场景分析

《Nginx内置变量应用场景分析》Nginx内置变量速查表,涵盖请求URI、客户端信息、服务器信息、文件路径、响应与性能等类别,这篇文章给大家介绍Nginx内置变量应用场景分析,感兴趣的朋友跟随小编一... 目录1. Nginx 内置变量速查表2. 核心变量详解与应用场景3. 实际应用举例4. 注意事项Ng

Java多种文件复制方式以及效率对比分析

《Java多种文件复制方式以及效率对比分析》本文总结了Java复制文件的多种方式,包括传统的字节流、字符流、NIO系列、第三方包中的FileUtils等,并提供了不同方式的效率比较,同时,还介绍了遍历... 目录1 背景2 概述3 遍历3.1listFiles()3.2list()3.3org.codeha