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