强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

C/C++ Linux 开发教程(GCC + CMake) / 智能指针与内存管理

智能指针与内存管理

1. 智能指针总览

类型所有权引用计数典型场景
std::unique_ptr独占单一所有者,工厂函数返回值
std::shared_ptr共享多个所有者共享同一资源
std::weak_ptr不拥有不影响计数打破循环引用、缓存
#include <iostream>
#include <memory>
#include <string>

int main() {
    // unique_ptr — 独占所有权
    std::unique_ptr<int> up = std::make_unique<int>(42);
    std::cout << "unique_ptr: " << *up << "\n";

    // std::unique_ptr<int> up2 = up;  // 编译错误:不可拷贝
    std::unique_ptr<int> up3 = std::move(up);  // 可移动
    std::cout << "移动后: " << *up3 << ", up=" << (up ? "有效" : "nullptr") << "\n";

    // shared_ptr — 共享所有权
    std::shared_ptr<int> sp = std::make_shared<int>(100);
    std::cout << "shared_ptr: " << *sp << ", 引用计数=" << sp.use_count() << "\n";

    {
        std::shared_ptr<int> sp2 = sp;  // 共享所有权
        std::cout << "sp2 作用域内: 引用计数=" << sp.use_count() << "\n";  // 2
    }
    std::cout << "sp2 离开后: 引用计数=" << sp.use_count() << "\n";  // 1

    // weak_ptr — 弱引用
    std::weak_ptr<int> wp = sp;
    std::cout << "weak_ptr expired? " << std::boolalpha << wp.expired() << "\n";  // false

    if (auto locked = wp.lock()) {
        std::cout << "锁定成功: " << *locked << "\n";
    }

    return 0;
}

编译运行:

g++ -std=c++17 -o demo demo.cpp && ./demo

2. std::unique_ptr 详解

unique_ptr 是零开销的智能指针(与裸指针大小相同),默认使用 delete 释放资源。

#include <iostream>
#include <memory>
#include <vector>

class Resource {
    std::string name_;

public:
    explicit Resource(const std::string& name) : name_(name) {
        std::cout << "构造: " << name_ << "\n";
    }

    ~Resource() {
        std::cout << "析构: " << name_ << "\n";
    }

    void use() const {
        std::cout << "使用: " << name_ << "\n";
    }
};

// 工厂函数:返回 unique_ptr
std::unique_ptr<Resource> createResource(const std::string& name) {
    return std::make_unique<Resource>(name);
}

// 接收所有权
void takeOwnership(std::unique_ptr<Resource> res) {
    res->use();
    // 函数结束时自动释放
}

int main() {
    // 基本使用
    auto r1 = std::make_unique<Resource>("R1");
    r1->use();

    // 移动所有权
    auto r2 = std::move(r1);
    if (!r1) {
        std::cout << "r1 已为空\n";
    }

    // 从工厂获取
    auto r3 = createResource("R3");

    // 传递所有权
    takeOwnership(std::move(r3));

    // unique_ptr 数组
    auto arr = std::make_unique<int[]>(10);
    for (int i = 0; i < 10; ++i) {
        arr[i] = i * i;
    }
    std::cout << "arr[5] = " << arr[5] << "\n";

    // 容器中使用
    std::vector<std::unique_ptr<Resource>> resources;
    resources.push_back(std::make_unique<Resource>("V1"));
    resources.push_back(std::make_unique<Resource>("V2"));

    for (const auto& r : resources) {
        r->use();
    }

    return 0;
}

💡 提示sizeof(std::unique_ptr<T>) == sizeof(T*),零额外开销。优先使用 unique_ptr,只在确实需要共享所有权时才用 shared_ptr


3. std::shared_ptr 详解

shared_ptr 使用引用计数管理资源,计数归零时释放资源。

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Widget {
    std::string id_;

public:
    explicit Widget(const std::string& id) : id_(id) {
        std::cout << "构造 Widget: " << id_ << "\n";
    }

    ~Widget() {
        std::cout << "析构 Widget: " << id_ << "\n";
    }

    const std::string& id() const { return id_; }
};

int main() {
    // make_shared 优于直接构造(单次内存分配)
    auto w1 = std::make_shared<Widget>("W1");
    std::cout << "引用计数: " << w1.use_count() << "\n";  // 1

    {
        auto w2 = w1;  // 拷贝,引用计数 +1
        auto w3 = w1;  // 拷贝,引用计数 +1
        std::cout << "引用计数: " << w1.use_count() << "\n";  // 3

        std::vector<std::shared_ptr<Widget>> vec;
        vec.push_back(w1);
        std::cout << "引用计数: " << w1.use_count() << "\n";  // 4
    }
    std::cout << "作用域结束后引用计数: " << w1.use_count() << "\n";  // 1

    // shared_ptr 与自定义删除器
    auto fileDeleter = [](FILE* fp) {
        if (fp) {
            std::cout << "关闭文件\n";
            fclose(fp);
        }
    };

    {
        std::shared_ptr<FILE> file(fopen("/tmp/test.txt", "w"), fileDeleter);
        if (file) {
            fprintf(file.get(), "Hello shared_ptr\n");
        }
    }  // 自动调用 fileDeleter

    return 0;
}

shared_ptr 的陷阱

#include <iostream>
#include <memory>

class Bad {
public:
    // ❌ 错误:从裸指针创建多个 shared_ptr(会导致双重释放)
    void wrongWay() {
        int* raw = new int(42);
        // std::shared_ptr<int> p1(raw);
        // std::shared_ptr<int> p2(raw);  // 两个 shared_ptr 各自管理同一资源!
    }

    // ✅ 正确:使用 make_shared 或从另一个 shared_ptr 拷贝
    void rightWay() {
        auto p1 = std::make_shared<int>(42);
        auto p2 = p1;  // 共享所有权
    }
};

// ❌ 不要返回成员的 shared_ptr(如果类本身由 shared_ptr 管理)
class Node {
public:
    std::shared_ptr<Node> next;

    // 危险:返回 this 的 shared_ptr 会导致独立的引用计数
    // std::shared_ptr<Node> getShared() { return std::shared_ptr<Node>(this); }

    // ✅ 正确:使用 enable_shared_from_this
};

class SafeNode : public std::enable_shared_from_this<SafeNode> {
public:
    std::shared_ptr<SafeNode> next;

    std::shared_ptr<SafeNode> getShared() {
        return shared_from_this();  // 安全地获取 shared_ptr
    }
};

int main() {
    auto node = std::make_shared<SafeNode>();
    auto same = node->getShared();
    std::cout << "引用计数: " << node.use_count() << "\n";  // 2

    return 0;
}

4. std::weak_ptr 打破循环引用

#include <iostream>
#include <memory>
#include <string>

// ❌ 循环引用导致内存泄漏
struct BadNode {
    std::string name;
    std::shared_ptr<BadNode> partner;  // 循环引用!

    explicit BadNode(const std::string& n) : name(n) {
        std::cout << "构造: " << name << "\n";
    }

    ~BadNode() {
        std::cout << "析构: " << name << "\n";
    }
};

// ✅ 使用 weak_ptr 打破循环
struct GoodNode {
    std::string name;
    std::weak_ptr<GoodNode> partner;  // 弱引用,不影响引用计数

    explicit GoodNode(const std::string& n) : name(n) {
        std::cout << "构造: " << name << "\n";
    }

    ~GoodNode() {
        std::cout << "析构: " << name << "\n";
    }

    void greet() {
        if (auto p = partner.lock()) {
            std::cout << name << " 说: 你好 " << p->name << "!\n";
        } else {
            std::cout << name << " 说: 伙伴已离开\n";
        }
    }
};

int main() {
    // 演示循环引用问题
    std::cout << "--- 循环引用 ---\n";
    {
        auto a = std::make_shared<BadNode>("Alice");
        auto b = std::make_shared<BadNode>("Bob");
        a->partner = b;
        b->partner = a;
        // 离开作用域后,a 和 b 的引用计数都是 2(互相持有)→ 永远不会降到 0 → 内存泄漏
    }
    std::cout << "---(如果有析构输出说明没有泄漏)---\n\n";

    // 使用 weak_ptr
    std::cout << "--- weak_ptr ---\n";
    {
        auto a = std::make_shared<GoodNode>("Alice");
        auto b = std::make_shared<GoodNode>("Bob");
        a->partner = b;
        b->partner = a;

        a->greet();  // Alice 说: 你好 Bob!
        b->greet();  // Bob 说: 你好 Alice!
    }
    std::cout << "--- 对象已正确析构 ---\n";

    // weak_ptr 在缓存中的应用
    std::cout << "\n--- 缓存场景 ---\n";
    std::weak_ptr<int> cache;
    {
        auto data = std::make_shared<int>(42);
        cache = data;

        if (auto locked = cache.lock()) {
            std::cout << "缓存命中: " << *locked << "\n";
        }
    }
    // data 已释放
    if (cache.expired()) {
        std::cout << "缓存已失效\n";
    }

    return 0;
}

5. 自定义删除器

#include <iostream>
#include <memory>
#include <cstdio>

// unique_ptr 自定义删除器(删除器是类型的一部分)
struct FileDeleter {
    void operator()(FILE* fp) const {
        if (fp) {
            std::cout << "关闭文件\n";
            std::fclose(fp);
        }
    }
};

void unique_ptr_custom_deleter() {
    // 删除器是模板参数
    std::unique_ptr<FILE, FileDeleter> file(
        std::fopen("/tmp/test.txt", "w")
    );
    if (file) {
        std::fprintf(file.get(), "Hello unique_ptr\n");
    }
    // 离开作用域时调用 FileDeleter
}

// shared_ptr 自定义删除器(删除器不是类型的一部分)
void shared_ptr_custom_deleter() {
    // Lambda 作为删除器
    std::shared_ptr<FILE> file(
        std::fopen("/tmp/test2.txt", "w"),
        [](FILE* fp) {
            if (fp) {
                std::cout << "shared_ptr 关闭文件\n";
                std::fclose(fp);
            }
        }
    );
    if (file) {
        std::fprintf(file.get(), "Hello shared_ptr\n");
    }
}

// 管理 C 数组
void manage_c_array() {
    // C++17 起 unique_ptr 默认支持数组
    auto arr = std::unique_ptr<int[]>(new int[5]{1, 2, 3, 4, 5});
    for (int i = 0; i < 5; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << "\n";

    // shared_ptr 管理数组需要自定义删除器(C++17 起可以用 default_delete)
    std::shared_ptr<int> arr2(new int[10], std::default_delete<int[]>());
}

int main() {
    unique_ptr_custom_deleter();
    shared_ptr_custom_deleter();
    manage_c_array();

    return 0;
}

6. 智能指针与多态

#include <iostream>
#include <memory>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;
    virtual void draw() const = 0;
    virtual double area() const = 0;
};

class Circle : public Shape {
    double radius_;
public:
    explicit Circle(double r) : radius_(r) {}
    void draw() const override { std::cout << "● r=" << radius_ << "\n"; }
    double area() const override { return 3.14159 * radius_ * radius_; }
};

class Rect : public Shape {
    double w_, h_;
public:
    Rect(double w, double h) : w_(w), h_(h) {}
    void draw() const override { std::cout << "■ " << w_ << "x" << h_ << "\n"; }
    double area() const override { return w_ * h_; }
};

// 工厂函数
std::unique_ptr<Shape> createShape(const std::string& type) {
    if (type == "circle") return std::make_unique<Circle>(5.0);
    if (type == "rect") return std::make_unique<Rect>(3.0, 4.0);
    return nullptr;
}

int main() {
    // unique_ptr 与多态
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(5.0));
    shapes.push_back(std::make_unique<Rect>(3.0, 4.0));
    shapes.push_back(createShape("circle"));

    double totalArea = 0;
    for (const auto& s : shapes) {
        s->draw();
        totalArea += s->area();
    }
    std::cout << "总面积: " << totalArea << "\n";

    // shared_ptr 与多态
    std::shared_ptr<Shape> sp = std::make_shared<Circle>(10.0);
    sp->draw();

    return 0;
}

⚠️ 注意:基类的析构函数必须是 virtual,否则通过基类智能指针删除派生类对象会导致未定义行为。


7. C++17 pmr 多态内存资源

#include <iostream>
#include <memory_resource>
#include <vector>
#include <string>

int main() {
    // 栈上缓冲区作为内存池
    char buffer[1024];
    std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};

    // 使用自定义内存资源的容器
    std::pmr::vector<int> vec{&pool};
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }

    std::cout << "pmr::vector: ";
    for (auto v : vec) std::cout << v << " ";
    std::cout << "\n";

    // 统计内存使用
    struct CountingResource : std::pmr::memory_resource {
        size_t allocated = 0;
        size_t deallocated = 0;

        void* do_allocate(size_t bytes, size_t alignment) override {
            allocated += bytes;
            return std::pmr::new_delete_resource()->allocate(bytes, alignment);
        }

        void do_deallocate(void* ptr, size_t bytes, size_t alignment) override {
            deallocated += bytes;
            std::pmr::new_delete_resource()->deallocate(ptr, bytes, alignment);
        }

        bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
            return this == &other;
        }
    };

    CountingResource counter;
    {
        std::pmr::vector<std::pmr::string> names{&counter};
        names.emplace_back("Alice");
        names.emplace_back("Bob");
        names.emplace_back("Charlie");

        std::cout << "已分配: " << counter.allocated << " 字节\n";
    }
    std::cout << "已释放: " << counter.deallocated << " 字节\n";

    return 0;
}

💡 提示:pmr 适用于需要精细控制内存分配的场景,如游戏引擎、嵌入式系统、高频交易系统。


8. 内存泄漏预防

问题原因解决方案
忘记 delete手动管理出错使用智能指针
循环引用shared_ptr 互相引用使用 weak_ptr
异常导致跳过 delete异常改变控制流RAII + 智能指针
数组 delete 用了 delete类型不匹配使用 delete[]vector
悬挂指针指向已释放内存使用智能指针

使用工具检测内存泄漏

# Valgrind
g++ -g -o demo demo.cpp
valgrind --leak-check=full ./demo

# AddressSanitizer (GCC/Clang)
g++ -std=c++17 -fsanitize=address -g -o demo demo.cpp
./demo
#include <iostream>
#include <memory>

// 使用 AddressSanitizer 检测此代码
void demonstrate_leak_detection() {
    // ❌ 泄漏
    // int* p = new int(42);
    // 忘记 delete p;

    // ✅ 无泄漏
    auto p = std::make_unique<int>(42);
    std::cout << *p << "\n";
    // 自动释放
}

int main() {
    demonstrate_leak_detection();
    return 0;
}

9. 智能指针最佳实践

#include <iostream>
#include <memory>
#include <vector>
#include <string>

class Database {
    std::string connStr_;

public:
    explicit Database(const std::string& connStr) : connStr_(connStr) {
        std::cout << "连接数据库: " << connStr_ << "\n";
    }

    ~Database() {
        std::cout << "断开数据库: " << connStr_ << "\n";
    }

    void query(const std::string& sql) {
        std::cout << "执行: " << sql << "\n";
    }
};

// ✅ 1. 工厂函数返回 unique_ptr
std::unique_ptr<Database> createDB(const std::string& connStr) {
    return std::make_unique<Database>(connStr);
}

// ✅ 2. 参数用引用或裸指针(不转移所有权)
class Service {
    Database& db_;  // 不拥有资源

public:
    explicit Service(Database& db) : db_(db) {}

    void run() {
        db_.query("SELECT * FROM users");
    }
};

// ✅ 3. 需要共享所有权时,用 shared_ptr
class Cache {
    std::shared_ptr<Database> db_;

public:
    explicit Cache(std::shared_ptr<Database> db) : db_(std::move(db)) {}
    void refresh() { db_->query("SELECT * FROM cache"); }
};

int main() {
    // 1. 优先使用 make_unique/make_shared
    auto db = createDB("localhost:5432");

    // 2. 非拥有引用
    Service svc(*db);
    svc.run();

    // 3. 共享所有权
    auto sharedDb = std::make_shared<Database>("shared:5432");
    Cache cache1(sharedDb);
    Cache cache2(sharedDb);
    cache1.refresh();
    cache2.refresh();

    // 4. 不要用的模式
    // ❌ 不要混用裸指针和智能指针
    // ❌ 不要从裸指针创建多个智能指针
    // ❌ 不要在栈上对象使用智能指针

    return 0;
}

智能指针选择速查表

需要指针语义?
├── 否 → 用栈对象(推荐)
└── 是 → 所有权转移?
         ├── 是 → std::unique_ptr
         └── 否 → 多个所有者?
                  ├── 否 → std::unique_ptr
                  └── 是 → 有循环引用?
                           ├── 是 → 打破循环用 std::weak_ptr
                           └── 否 → std::shared_ptr

扩展阅读