C++11——智能指针和function库
目录
一、智能指针
1. std::unique_ptr(独占所有权指针)
3. std::weak_ptr(弱引用指针)
关键区别总结
最佳实践
基本用法
可封装的对象类型
核心特性
示例代码
1. 基本调用
2. 结合 Lambda 和参数传递
3. 作为回调函数
与模板的对比
使用场景
注意事项
总结
一、智能指针
shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp 空智能指针,可以指向类型为T的对象
unique_ptr<T> up
p 将p用作一条判断,若p指向一个对象,则为true
*p 解引用p。获得它指向的对象
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
swap(p,q) 交换p和q中的指针
p.swap(q)
share_ptr独有的操作
make_shared<T>(angs) 返回一个 shared_ptr,指向一个动态分配的类型为了的对象。使用args 初始化此对象
shared_ptr<T> p(q) p是shared ptrq的拷贝:此操作会递增q中的计数器,g中的指针必须能转换为T*
p=q p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放
p.unique()
p.use_count() 若p.use_count()为1,返true:否则返回false返回与p共享对象的智能指针数量;可能很慢,主要用于调试
(参见4112节,第143页
1. std::unique_ptr
(独占所有权指针)
-
特点:
-
独占资源的所有权,同一时间只能有一个
unique_ptr
指向某个对象。 -
不能拷贝构造或赋值,但可以通过
std::move
转移所有权。 -
生命周期结束时(超出作用域或被重置),自动释放管理的资源。
-
-
适用场景:
-
管理动态分配的资源,明确所有权单一的情况。
-
替代 C++98 的
auto_ptr
(已弃用)。
-
-
示例:
#include <memory> void example_unique_ptr() { std::unique_ptr<int> ptr1(new int(42)); // 创建 unique_ptr std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权 if (ptr1) { // ptr1 不再拥有资源,不会执行此处 } if (ptr2) { *ptr2 = 10; // 修改指向的值 } } // 自动释放 ptr2 的资源
2. std::shared_ptr
(共享所有权指针)
-
特点:
-
通过引用计数(reference counting)管理资源,允许多个
shared_ptr
共享同一对象。 -
当最后一个
shared_ptr
被销毁或重置时,资源被释放。 -
可以通过
std::make_shared
高效创建(减少内存分配次数)。
-
-
适用场景:
-
需要多个指针共享同一资源的场景。
-
需要传递资源所有权但不确定何时释放的情况。
-
-
示例:
#include <memory> void example_shared_ptr() { std::shared_ptr<int> ptr1 = std::make_shared<int>(42); // 推荐使用 make_shared std::shared_ptr<int> ptr2 = ptr1; // 引用计数 +1 std::cout << ptr1.use_count() << std::endl; // 输出 2 } // ptr1 和 ptr2 销毁,引用计数归零,资源释放
3. std::weak_ptr
(弱引用指针)
-
特点:
-
指向
shared_ptr
管理的资源,但不增加引用计数。 -
用于解决
shared_ptr
的循环引用问题(如两个对象相互持有对方的shared_ptr
)。 -
需要通过
lock()
方法获取临时的shared_ptr
来访问资源。
-
-
适用场景:
-
需要观察
shared_ptr
资源但不影响其生命周期。 -
打破循环引用。
-
-
示例:
#include <memory> void example_weak_ptr() { std::shared_ptr<int> shared = std::make_shared<int>(42); std::weak_ptr<int> weak = shared; if (auto temp = weak.lock()) { // 转换为 shared_ptr std::cout << *temp << std::endl; // 输出 42 } shared.reset(); // 释放资源 if (weak.expired()) { // 检查资源是否已被释放 std::cout << "Resource is gone." << std::endl; } }
关键区别总结
智能指针 | 所有权 | 拷贝/赋值 | 性能开销 | 适用场景 |
---|---|---|---|---|
unique_ptr | 独占 | 只能移动 | 无 | 单一所有权,明确生命周期 |
shared_ptr | 共享 | 允许拷贝 | 引用计数 | 共享所有权,不确定生命周期 |
weak_ptr | 无(仅观察) | 允许拷贝 | 无计数 | 解决循环引用,观察资源 |
最佳实践
-
优先使用
unique_ptr
:默认情况下使用unique_ptr
,仅在需要共享时使用shared_ptr
。 -
避免裸指针与智能指针混用:不要将智能指针管理的资源通过裸指针传递。
-
使用
make_shared
和make_unique
(C++14+):cpp
复制
auto ptr = std::make_unique<int>(42); // C++14 支持 auto ptr2 = std::make_shared<int>(42); // 减少内存分配次数
-
注意循环引用:如果两个对象互相持有
shared_ptr
,使用weak_ptr
打破循环。
基本用法
-
头文件:
#include <functional>
-
定义:
std::function<返回值类型(参数类型列表)> func;
例如:
std::function<int(int, int)> func; // 封装一个接受两个 int、返回 int 的可调用对象
可封装的对象类型
std::function
可以封装以下类型的可调用对象:
-
普通函数:
int add(int a, int b) { return a + b; } std::function<int(int, int)> func = add;
-
Lambda 表达式:
std::function<int(int, int)> func = [](int a, int b) { return a * b; };
-
函数对象(仿函数):
struct Multiply { int operator()(int a, int b) { return a * b; } }; Multiply mul; std::function<int(int, int)> func = mul;
-
类的成员函数(需结合
std::bind
):class Calculator { public: int subtract(int a, int b) { return a - b; } }; Calculator calc; auto bound_func = std::bind(&Calculator::subtract, &calc, std::placeholders::_1, std::placeholders::_2); std::function<int(int, int)> func = bound_func;
-
静态成员函数(直接赋值):
class Math { public: static int divide(int a, int b) { return a / b; } }; std::function<int(int, int)> func = Math::divide;
核心特性
-
类型擦除:
-
std::function
隐藏了底层可调用对象的具体类型,通过统一的接口调用。 -
例如,无论是 Lambda 还是函数指针,都可以被同一个
std::function
类型包装。
-
-
运行时多态:
-
可以在运行时动态绑定不同的函数或对象,适用于回调机制和事件驱动编程。
-
-
空状态检查:
-
默认初始化的
std::function
为空,调用空function
会抛出std::bad_function_call
异常。 -
使用
operator bool()
检查是否可调用:if (func) { func(2, 3); // 安全调用 }
-
示例代码
1. 基本调用
#include <iostream>
#include <functional>
void hello() {
std::cout << "Hello, World!" << std::endl;
}
int main() {
std::function<void()> func = hello;
func(); // 输出 "Hello, World!"
return 0;
}
2. 结合 Lambda 和参数传递
#include <functional>
int main() {
std::function<int(int, int)> operation;
// 动态绑定加法
operation = [](int a, int b) { return a + b; };
std::cout << operation(3, 4) << std::endl; // 输出 7
// 动态绑定乘法
operation = [](int a, int b) { return a * b; };
std::cout << operation(3, 4) << std::endl; // 输出 12
return 0;
}
3. 作为回调函数
#include <functional>
#include <vector>
class Button {
public:
using Callback = std::function<void()>;
void setOnClick(Callback callback) {
onClick = callback;
}
void click() {
if (onClick) onClick();
}
private:
Callback onClick;
};
int main() {
Button btn;
btn.setOnClick([]() { std::cout << "Button clicked!" << std::endl; });
btn.click(); // 输出 "Button clicked!"
return 0;
}
与模板的对比
-
模板:在编译时确定类型,性能更高,但无法动态更换函数类型。
-
std::function
:运行时动态绑定,灵活性更高,但有轻微性能开销(类型擦除和虚函数调用)。
使用场景
-
回调机制(如 GUI 事件处理、网络请求完成回调)。
-
策略模式:动态切换算法或行为。
-
函数注册表:将不同函数存储在容器中统一调用。
-
延迟执行:将函数绑定到某个条件触发时执行。
注意事项
-
性能:
std::function
的调用比直接调用函数或模板稍慢(涉及间接调用),但对大多数场景影响不大。 -
内存分配:若封装的函数对象较大(如捕获大量变量的 Lambda),
std::function
可能触发堆内存分配。 -
绑定成员函数:必须结合
std::bind
或 Lambda 表达式,并传递对象实例的指针或引用。
总结
std::function
是 C++11 中实现类型安全的函数抽象的核心工具,它通过统一的接口封装任意可调用对象,极大提升了代码的灵活性和可维护性。合理使用 std::function
可以简化回调设计,实现动态行为绑定,但需注意其性能和使用场景的平衡。