在 Chromium 的 base
库中,base::BindRepeating
和 base::BindOnce
是两种用于创建回调的模板函数,它们的主要区别在于 回调的调用语义 和 所有权模型。以下是它们的核心区别和适用场景:
1. 核心区别
特性 | base::BindOnce |
base::BindRepeating |
---|---|---|
调用次数 | 仅能调用 一次(移动语义) | 可调用 多次(复制语义) |
所有权转移 | 绑定参数和回调本身 只能移动 (std::move ) |
绑定参数可复制,回调可多次持有 |
性能 | 更高效(避免引用计数开销) | 可能有额外开销(如引用计数) |
典型用途 | 异步任务、单次回调(如 PostTask ) |
事件监听、需要多次触发的回调(如信号槽) |
是否支持 WeakPtr |
✅ 需显式调用 base::BindOnce + std::move |
✅ 直接支持 |
2. 详细对比
(1) 调用次数限制
-
base::BindOnce
-
生成的
base::OnceCallback
只能调用一次 ,调用后失效(变为nullptr
)。 -
适用于 一次性操作(如异步任务完成后的回调)。
base::OnceCallback<void(int)> callback = base::BindOnce([](int x) { LOG(INFO) << x; }); std::move(callback).Run(42); // 调用一次,之后 callback 失效 // std::move(callback).Run(43); // 错误!callback 已无效
-
-
base::BindRepeating
-
生成的
base::RepeatingCallback
可多次调用。 -
适用于 重复事件(如按钮点击、信号通知)。
base::RepeatingCallback<void(int)> callback = base::BindRepeating([](int x) { LOG(INFO) << x; }); callback.Run(42); // 第一次调用 callback.Run(43); // 第二次调用(合法)
-
(2) 所有权和参数传递
-
base::BindOnce
-
绑定参数和回调本身 通过移动语义传递 (
std::move
)。 -
适合传递独占资源(如
std::unique_ptr
)。
auto task = std::make_unique<Task>(); base::OnceCallback<void()> callback = base::BindOnce( [](std::unique_ptr<Task> task) { task->Execute(); }, std::move(task) // 移交所有权 );
-
-
base::BindRepeating
-
绑定参数 需支持复制 (或使用
base::RetainedRef
等包装器)。 -
无法直接绑定
std::unique_ptr
(除非手动管理生命周期)。
int value = 42; base::RepeatingCallback<void()> callback = base::BindRepeating( [](int x) { LOG(INFO) << x; }, value // 值被复制 );
-
(3) 与 WeakPtr
的结合
-
base::BindOnce
+WeakPtr
-
需要显式使用
std::move
传递WeakPtr
:base::BindOnce(&MyClass::OnDone, std::move(weak_ptr));
-
调用时自动检查
WeakPtr
有效性(若对象已销毁,回调不执行)。
-
-
base::BindRepeating
+WeakPtr
-
直接绑定
WeakPtr
,每次调用都会检查有效性:base::BindRepeating(&MyClass::OnEvent, weak_ptr);
-
3. 如何选择?
场景 | 推荐使用 | 原因 |
---|---|---|
异步任务回调(如 PostTask ) |
base::BindOnce |
任务通常只执行一次,避免不必要的开销 |
事件监听(如按钮点击) | base::BindRepeating |
事件可能多次触发 |
需要传递 std::unique_ptr |
base::BindOnce |
移动语义更安全 |
需要支持跨线程回调 | 两者均可 | 但需配合 WeakPtr 或 base::RetainedRef 管理生命周期 |
4. 代码示例
(1) base::BindOnce
典型用法
// 异步任务完成后回调(单次)
void OnTaskComplete(base::OnceCallback<void(int)> callback) {
std::move(callback).Run(42); // 调用后 callback 失效
}
// 绑定到 OnceCallback
auto callback = base::BindOnce([](int x) { LOG(INFO) << "Result: " << x; });
OnTaskComplete(std::move(callback));
(2) base::BindRepeating
典型用法
// 事件监听(多次触发)
class Button {
public:
void SetClickCallback(base::RepeatingCallback<void()> callback) {
click_callback_ = std::move(callback);
}
void Click() { click_callback_.Run(); }
private:
base::RepeatingCallback<void()> click_callback_;
};
// 绑定到 RepeatingCallback
Button button;
button.SetClickCallback(base::BindRepeating([]() { LOG(INFO) << "Clicked!"; }));
button.Click(); // 多次触发
好的,我将从 源码层面 深入分析 base::BindOnce
和 base::BindRepeating
的实现机制,结合 Chromium 的 base
库代码(基于最新稳定版本),解析它们的核心设计差异和性能关键点。
5. 核心类与模板结构
(1) 回调的基类:base::Callback
Chromium 的回调系统通过模板类 base::OnceCallback
和 base::RepeatingCallback
实现,二者均继承自 base::Callback
的模板特化。
源码路径:base/functional/callback.h
template <typename Signature>
class OnceCallback; // 只能调用一次
template <typename Signature>
class RepeatingCallback; // 可多次调用
(2) 绑定工厂:base::Bind
系列
base::BindOnce
和 base::BindRepeating
是工厂函数,生成对应的回调对象。
关键源码片段:
// base/bind.h
template <typename Functor, typename... Args>
inline OnceCallback<MakeUnboundRunType<Functor, Args...>>
BindOnce(Functor&& functor, Args&&... args) {
return BindImpl<OnceCallback>(std::forward<Functor>(functor),
std::forward<Args>(args)...);
}
template <typename Functor, typename... Args>
inline RepeatingCallback<MakeUnboundRunType<Functor, Args...>>
BindRepeating(Functor&& functor, Args&&... args) {
return BindImpl<RepeatingCallback>(std::forward<Functor>(functor),
std::forward<Args>(args)...);
}
6. 底层实现机制
(1) 回调存储:BindState
所有绑定的参数和函数对象通过 BindState
存储,这是一个引用计数的内部类。
源码路径:base/bind_internal.h
template <typename Functor, typename... BoundArgs>
struct BindState {
Functor functor_;
std::tuple<BoundArgs...> bound_args_;
// 引用计数控制(RepeatingCallback 使用)
mutable scoped_refptr<RefCountedBase> ref_count_;
};
-
OnceCallback
:直接持有BindState
的独占所有权(类似std::unique_ptr
)。 -
RepeatingCallback
:通过scoped_refptr
共享BindState
(类似std::shared_ptr
)。
(2) 调用逻辑
回调的调用通过模板特化的 Run()
方法实现:
// OnceCallback 的调用(移动语义)
template <typename R, typename... Args>
R OnceCallback<R(Args...)>::Run(Args... args) && {
// 调用后销毁内部状态
auto state = std::move(bind_state_);
return state->functor_.Run(std::forward<Args>(args)...);
}
// RepeatingCallback 的调用(复制语义)
template <typename R, typename... Args>
R RepeatingCallback<R(Args...)>::Run(Args... args) const {
// 无状态转移,可多次调用
return bind_state_->functor_.Run(std::forward<Args>(args)...);
}
7. 关键性能差异
(1) 内存管理
回调类型 | 存储方式 | 开销 |
---|---|---|
OnceCallback |
独占 BindState (移动) |
无原子操作,无引用计数 |
RepeatingCallback |
共享 BindState (引用计数) |
需要原子操作维护 ref_count_ |
(2) 参数传递优化
-
OnceCallback
:支持移动语义绑定std::unique_ptr
等独占类型。auto ptr = std::make_unique<int>(42); auto callback = base::BindOnce([](std::unique_ptr<int> p) {}, std::move(ptr));
-
RepeatingCallback
:要求参数可复制(或使用base::RetainedRef
包装)。
8. 线程安全性分析
(1) 回调本身的线程安全
-
OnceCallback
:移动后原回调失效,跨线程传递需显式std::move
。 -
RepeatingCallback
:可安全复制到多个线程,但调用时需自行同步。
(2) 与 WeakPtr
的结合
-
OnceCallback
:绑定WeakPtr
时自动生成无效回调(调用时检查):// 内部实现:调用前检查 WeakPtr 是否有效 template <typename T> void InvokeWithWeakPtr(T* obj) { if (!obj) return; // WeakPtr 已失效 functor_.Run(obj, std::forward<Args>(args)...); }
-
RepeatingCallback
:每次调用都会检查WeakPtr
。
9. 设计哲学总结
-
OnceCallback
-
零开销抽象:通过移动语义避免引用计数。
-
强制单次调用 :防止资源泄漏(如重复释放
std::unique_ptr
)。
-
-
RepeatingCallback
-
灵活性优先:支持多次调用和跨线程共享。
-
代价是性能:引用计数和参数复制可能引入开销。
-
10. 实际应用示例
(1) 单次任务回调(OnceCallback
)
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce([](std::unique_ptr<Data> data) {
ProcessData(std::move(data));
},
std::make_unique<Data>()));
(2) 事件监听(RepeatingCallback
)
class Button {
public:
void SetClickCallback(base::RepeatingClosure callback) {
callback_ = std::move(callback);
}
void Click() { callback_.Run(); }
private:
base::RepeatingClosure callback_;
};
11. 从源码学到的优化技巧
-
优先用
OnceCallback
:除非需要多次调用,否则避免引用计数开销。 -
移动语义绑定 :对独占资源(如
std::unique_ptr
)使用BindOnce
。 -
避免跨线程持有
RepeatingCallback
:改用PostTask
+OnceCallback
减少竞争。
通过源码分析可见,Chromium 通过 模板元编程 和 移动语义 极致优化了回调性能,而 OnceCallback
/RepeatingCallback
的区分正是对 资源所有权 和 调用语义 的精确控制。