C++11 智能指针
在 C++ 中,智能指针(Smart Pointers)是用来自动管理动态分配内存的工具,可以减少内存泄漏的风险。C++标准库提供了几种常见的智能指针,包括 std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
,它们各自有不同的特性和用途。下面我们将详细对比解释这些智能指针。
std::unique_ptr
std::unique_ptr
是一种独占性的智能指针,意味着同一时刻只有一个 unique_ptr
可以拥有某个对象的所有权。它遵循 RAII(Resource Acquisition Is Initialization)原则,当 unique_ptr
被销毁时,它所管理的对象会被自动释放。
其特点:
- 独占所有权:同一个对象只能被一个
unique_ptr
拥有。 - 不能拷贝:
unique_ptr
不能被复制,因为它拥有唯一的所有权。尝试拷贝会导致编译错误。 - 支持移动语义:可以通过移动构造函数或者移动赋值操作符将所有权从一个
unique_ptr
移动到另一个。(?) - 自动释放内存:当
unique_ptr
被销毁时,它会自动释放所管理的对象。- 注意结合 std:: make_unique 和 std:: move 使用!
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int val) : value(val) {
std::cout << "MyClass constructed with value: " << value << std::endl;
}
~MyClass() {
std::cout << "MyClass destructed with value: " << value << std::endl;
}
void showValue() const {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(10); // ptr1 拥有 MyClass 的所有权
std::cout << "Moving ownership to ptr2..." << std::endl;
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 转移所有权
// ptr1 现在为空,不能再访问对象
if (ptr1) {
ptr1->showValue();
} else {
std::cout << "ptr1 is now null." << std::endl;
}
// ptr2 现在拥有 MyClass 对象的所有权
ptr2->showValue();
return 0;
}
std::shared_ptr
std::shared_ptr
是一种共享所有权的智能指针,多个 shared_ptr
可以共享同一个对象的所有权。只有当最后一个 shared_ptr
被销毁时,所管理的对象才会被释放。
特点:
- 共享所有权:多个
shared_ptr
可以指向同一个对象,每个shared_ptr
都会增加一个引用计数。 - 引用计数:
shared_ptr
会跟踪有多少个shared_ptr
指向同一个对象,当引用计数归零时,对象会被自动销毁。 - 拷贝与赋值:
shared_ptr
支持拷贝和赋值操作,每次拷贝都会增加引用计数。 - 性能开销:由于引用计数的管理,
shared_ptr
会带来额外的性能开销(例如原子操作)。
释放需要手动吗?
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int x) : x_(x) {
std::cout << "MyClass(" << x_ << ") constructed\n";
}
~MyClass() {
std::cout << "MyClass(" << x_ << ") destructed\n";
}
private:
int x_;
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(10);
{
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
std::cout << "Inside inner scope\n";
} // ptr2 离开作用域,引用计数减少
std::cout << "Outside inner scope\n";
return 0;
}
说明:
ptr1
和ptr2
共享对同一个MyClass
对象的所有权。- 当
ptr2
离开作用域时,它的引用计数减少,但对象不会立即销毁,因为ptr1
仍然指向它。 - 当
ptr1
离开作用域时,引用计数归零,MyClass
对象被销毁。
问题:shared_ptr 引起循环计数问题:
#include <iostream>
#include <memory>
class A;
class B {
public:
std::shared_ptr<A> a_ptr;
B() {
std::cout << "B constructed\n";
}
~B() {
std::cout << "B destructed\n";
}
};
class A {
public:
std::shared_ptr<B> b_ptr;
A() {
std::cout << "A constructed\n";
}
~A() {
std::cout << "A destructed\n";
}
};
int main() {
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b; // A -> B
b->a_ptr = a; // B -> A
// A 和 B 形成循环引用,内存无法释放
} // 这里的 a 和 b 应该销毁,但因为循环引用,它们的引用计数永远不为 0
std::cout << "End of scope\n";
return 0;
}
std::weak_ptr
std::weak_ptr
是一种弱引用的智能指针,它与 shared_ptr
配合使用,用来避免循环引用。weak_ptr
不会改变引用计数,它只提供对 shared_ptr
所管理对象的访问。如果对象被销毁,weak_ptr
就不再有效。
特点:
- 不影响引用计数:
weak_ptr
不会增加或减少引用计数,它只是一个“观察者”。 - 用于打破循环引用:
weak_ptr
主要用于解决shared_ptr
引起的循环引用问题。例如,当两个对象通过shared_ptr
相互引用时,会导致内存无法释放。使用weak_ptr
可以避免这种情况。 - 转换为
shared_ptr
:weak_ptr
可以通过lock()
方法转换为shared_ptr
。如果对象已经被销毁,lock()
返回一个空的shared_ptr
。
使用 weak_ptr 打破循环引用:
#include <iostream>
#include <memory>
class A;
class B {
public:
std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用
B() {
std::cout << "B constructed\n";
}
~B() {
std::cout << "B destructed\n";
}
};
class A {
public:
std::shared_ptr<B> b_ptr;
A() {
std::cout << "A constructed\n";
}
~A() {
std::cout << "A destructed\n";
}
};
int main() {
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b; // A -> B
b->a_ptr = a; // B -> A,但这里是 weak_ptr,避免了循环引用
// 由于没有循环引用,内存可以正确释放
} // A 和 B 都会被销毁
std::cout << "End of scope\n";
return 0;
}
对比总结
特性 | std::unique_ptr | std::shared_ptr | std::weak_ptr |
---|---|---|---|
所有权管理 | 独占所有权 | 共享所有权 | 不拥有对象,辅助管理共享所有权 |
拷贝行为 | 不支持拷贝,只能移动 | 支持拷贝,增加引用计数 | 不影响引用计数,只是弱引用 |
引用计数 | 无引用计数 | 有引用计数,自动销毁对象 | 无引用计数,仅观察对象是否仍存在 |
适用场景 | 需要独占所有权,简单的对象管理 | 需要共享所有权的复杂对象管理 | 解决 shared_ptr 引起的循环引用问题 |
性能开销 | 最小,只有对象本身的管理 | 稍大,包含引用计数的管理开销 | 极小,仅提供观察者功能 |
评论