BindOnce and callBack
参考文档:D:\Chromium\src\docs\callback.md
传递 base::{Once,Repeating} 如果所有权转移,则按值回调对象;否则,通过 引用传递
//Foo只是指向cb,但是不存储,也不消耗它 bool Foo(const base::OnceCallback<void(int)>& cb) { return cb.is_null(); } //Bar取得cb所有权,g_cb存储了他 base::RepeatingCallback<void(int)> g_cb; void Bar(base::RepeatingCallback<void(int)> cb) { g_cb = std::move(cb); } //Baz 取地所有权, 消耗它通过执行Run() void Baz(base::OnceCallback<void(int)> cb) { std::move(cb).Run(42); } //PostTask取得所有权 void Qux(base::RepeatingCallback<void(int)> cb) { PostTask(FROM_HERE, base::BindOnce(cb, 42)); PostTask(FROM_HERE, base::BindOnce(std::move(cb), 43)); }
将 base::{Once,Repeating}Callback 对象传递给函数参数时,如果不需要保留对它的引用,请使用 std::move() ,否则直接传递对象。
当函数需要独占所有权并且您没有通过移动传递回调时,您可能会看到编译错误。
请注意,移出的 base::{Once,Repeating}Callback 变为 null,就好像它的 Reset() 方法已被调用。之后,它的 is_null() 方法将返回 true,其 operator bool() 将返回 false。
链接回调
当您希望按顺序运行 2 个回调时,可以通过使用 Then() 将它们连接在一起成为一个回调。在 base::OnceCallback 上调用 Then() 会加入第二个回调,
该回调将与第一个回调一起运行,但在第一个回调之后。第一个回调的返回值传递给第二个,第二个回调的返回值在最后返回。更具体地说,
调用 a.Then(b) 会产生一个新的 base::OnceCallback,它将运行 b(a());,从 b 返回结果。
此示例使用 Then() 将 2 个 base::OnceCallbacks 连接在一起:
int Floor(float f) { return std::floor(f); } std::string IntToString(int i) { return base::NumberToString(i); } base::OnceCallback<int(float)> first = base::BindOnce(&Floor); base::OnceCallback<std::string(int)> second = base::BindOnce(&IntToString);
这将运行|first|,运行并将结果传递给|second|,然后返回|second| 的结果。
std::string r = std::move(first).Then(std::move(second)).Run(3.5f);
|r|将是"3"。 |第一|和 |第二|现在都是空的,因为它们是用于执行连接操作。
同样, Then() 也适用于 base::RepeatingCallback;但是,加入的回调也必须是 base::RepeatingCallback 以确保可以多次调用生成的回调。
此示例使用 Then() 将 2 个 base::RepeatingCallbacks 连接在一起:
int Floor(float f) { return std::floor(f); } std::string IntToString(int i) { return base::NumberToString(i); } base::RepeatingCallback<int(float)> first = base::BindRepeating(&Floor); base::RepeatingCallback<std::string(int)> second = base::BindRepeating(&IntToString); // 这会创建一个 RepeatingCallback,它将运行 |first|,运行并传递 // 结果到|second|,然后从|second| 返回结果。 base::RepeatingCallback<std::string(float)> joined = std::move(first).Then(std::move(second)); // //|第一|和 |第二|现在都为空,因为它们被消耗来执行连接操作。 //这会运行最初绑定到 |first| 的函子,然后是 |second| std::string r = joined.Run(3.5); // |r| will be "3". //多次调用它是有效的,因为所有涉及的回调都是base::RepeatingCallbacks。 r = joined.Run(2.5);
// |r| is set to "2".
在上面的示例中,使用 std::move() 将 base::RepeatingCallback 强制转换为 r 值会导致 Then() 破坏原始回调,与加入 base::OnceCallbacks 时发生的方式相同。 然而,由于 base::RepeatingCallback 可以运行多次,它也可以非破坏性地加入。
int Floor(float f) { return std::floor(f); } std::string IntToString(int i) { return base::NumberToString(i); } base::RepeatingCallback<int(float)> first = base::BindRepeating(&Floor); base::RepeatingCallback<std::string(int)> second = base::BindRepeating(&IntToString); std::string r = first.Then(second).Run(3.5f); int i = first.Run(5.5); std::string s = second.Run(9);
如果第二个回调不想从第一个回调接收值,您可以使用 base::IgnoreResult 在运行两者之间删除返回值。
base::RepeatingCallback<int()> first = base::BindRepeating([](){ return 5; }); //不想要接收结果 base::RepeatingClosure second = base::BindRepeating([](){}); //这不会编译,因为 |second| 无法接收到|first|返回值 // first.Then(second).Run(); // 我们可以从 |first| 中删除结果 在第二次运行之前。 base::BindRepeating(base::IgnoreResult(first)).Then(second).Run(); // 这将有效地创建一个回调,当 Run() 将调用该回调 // `first(); second();` 而不是`second(first());`。
注意 |first| 的返回值 在上面的例子中会丢失,并且会在 |second| 之前被销毁 正在运行。 如果你想要 |first| 的返回值 在运行两个 |first| 后被保存并最终返回 和 |second|,
那么您将需要一个原语,例如 base::PassThrough CL 中的 base::PassThrough<T>() 助手。 如果这对您有帮助,请让 danakj@chromium.org 知道或 ping CL。
跨不同任务运行器链接回调
// 不同线程的任务运行器。 scoped_refptr<base::SequencedTaskRunner> other_task_runner = ...; //一个计算一些有趣结果的函数,除了它只能运行安全地来自 `other_task_runner` 而不是当前线程。 int ComputeResult(); base::OnceCallback<int()> compute_result_cb = base::BindOnce(&ComputeResult); // 当前线程的任务运行器。 scoped_refptr<base::SequencedTaskRunner> current_task_runner = base::SequencedTaskRunnerHandle::Get(); // 一个接受结果的函数,除了它只能从当前线程安全运行。 void ProvideResult(int result); base::OnceCallback<void(int)> provide_result_cb = base::BindOnce(&ProvideResult);
使用 Then() 直接加入 compute_result_cb 和 provide_result_cb 是不合适的。 ComputeResult() 和 ProvideResult() 将在不安全的同一线程上运行。 但是,base::BindPostTask() 可用于确保 provide_result_cb 将在 current_task_runner 上运行。
// 以下两条语句将任务发布到 `other_task_runner` 以运行`任务`。 这将在不同的线程上调用 ComputeResult() 以获取结果值然后将任务发布回 `current_task_runner` 以调用提供结果()和结果。 OnceClosure task = std::move(compute_result_cb) .Then(base::BindPostTask(current_task_runner, std::move(provide_result_cb))); other_task_runner->PostTask(FROM_HERE, std::move(task));
将 OnceCallback 一分为二
如果回调只运行一次,
但是需要对回调进行两个引用,使用 base::OnceCallback 可以比 base::RepeatingCallback 更清晰,从意图和语义的角度来看。 base::SplitOnceCallback() 接受一个 base::OnceCallback 并返回一对具有相同签名的回调。当运行返回的回调中的任何一个时,都会调用原始回调。 运行剩余的回调将导致崩溃。 这在将 base::OnceCallback 传递给可能拥有或不拥有回调所有权的函数时很有用。 例如,当对象创建可能失败时:
std::unique_ptr<FooTask> CreateFooTask(base::OnceClosure task) { std::pair<base::OnceClosure,base::OnceClosure> split = base::SplitOnceCallback(std::move(task)); std::unique_ptr<FooTask> foo = TryCreateFooTask(std::move(split.first)); if (foo) return foo; return CreateFallbackFooTask(std::move(split.second)); }
虽然最好使用单个回调来报告成功/失败,但某些 API 已经采用多个回调。 base::SplitOnceCallback() 可用于拆分完成回调并在这种情况下提供帮助:
using StatusCallback = base::OnceCallback<void(FooStatus)>; void DoOperation(StatusCallback done_cb) { std::pair<StatusCallback, StatusCallback> split = base::SplitOnceCallback(std::move(done_cb)); InnerWork(BindOnce(std::move(split.first), STATUS_OK), BindOnce(std::move(split.second), STATUS_ABORTED)); } void InnerWork(base::OnceClosure work_done_cb, base::OnceClosure work_aborted_cb);
基本资料的快速参考
绑定一个裸函数
int Return5() { return 5; } base::OnceCallback<int()> func_cb = base::BindOnce(&Return5); LOG(INFO) << std::move(func_cb).Run(); // Prints 5.
int Return5() { return 5; } base::RepeatingCallback<int()> func_cb = base::BindRepeating(&Return5); LOG(INFO) << func_cb.Run(); // Prints 5.
绑定无捕获的 Lambda
base::Callback<int()> lambda_cb = base::Bind([] { return 4; }); LOG(INFO) << lambda_cb.Run(); // Print 4. base::OnceCallback<int()> lambda_cb2 = base::BindOnce([] { return 3; }); LOG(INFO) << std::move(lambda_cb2).Run(); // Print 3. base::OnceCallback<int()> lambda_cb3 = base::BindOnce([] { return 2; }); base::OnceCallback<int(base::OnceCallback<int()>)> lambda_cb4 = base::BindOnce( [](base::OnceCallback<int()> callback) { return std::move(callback).Run(); }, std::move(lambda_cb3)); LOG(INFO) << std::move(lambda_cb4).Run(); // Print 2.
绑定一个捕获的 Lambda(测试)
#include "base/test/bind.h" int i = 2; base::Callback<void()> lambda_cb = base::BindLambdaForTesting([&]() { i++; }); lambda_cb.Run(); LOG(INFO) << i; // Print 3;
绑定一个类方法
bind 的第一个参数是要调用的成员函数,第二个参数是要调用它的对象。
class Ref : public base::RefCountedThreadSafe<Ref> { public: int Foo() { return 3; } }; scoped_refptr<Ref> ref = new Ref(); base::Callback<void()> ref_cb = base::Bind(&Ref::Foo, ref); LOG(INFO) << ref_cb.Run(); // Prints out 3.
默认情况下,该对象必须支持 RefCounted,否则您将收到编译器错误。 如果您在线程之间传递,请确保它是 RefCountedThreadSafe! 如果您不想使用引用计数,请参阅下面的“成员函数的高级绑定”。
运行回调
回调可以使用它们的 Run 方法运行,该方法与回调的模板参数具有相同的签名。 请注意, base::OnceCallback::Run 使用回调对象,并且只能在回调右值上调用。
void DoSomething(const base::Callback<void(int, std::string)>& callback) { callback.Run(5, "hello"); } void DoSomethingOther(base::OnceCallback<void(int, std::string)> callback) { std::move(callback).Run(5, "hello"); }
RepeatingCallbacks 可以多次运行(运行时它们不会被删除或标记)。 但是,这排除了使用 base::Passed (见下文)
void DoSomething(const base::RepeatingCallback<double(double)>& callback) { double myresult = callback.Run(3.14159); myresult += callback.Run(2.71828); }
如果运行回调可能导致其自身的破坏(例如,如果回调接收者删除了回调所属的对象),
则应将回调移动或复制到堆栈中,然后才能安全地调用它。
(请注意,这只是 RepeatingCallbacks 的一个问题,因为总是必须移动 OnceCallback 才能执行。)
void Foo::RunCallback() { std::move(&foo_deleter_callback_).Run(); }
创建一个什么都不做的回调
有时您需要一个在运行时不执行任何操作的回调(例如,不关心通知某些类型事件的测试代码)。 传递正确类型的默认构造回调可能很诱人:
using MyCallback = base::OnceCallback<void(bool arg)>; void MyFunction(MyCallback callback) { std::move(callback).Run(true); // Uh oh... } ... MyFunction(MyCallback()); // ...this will crash when Run()!
默认构造的回调是空的,因此不能是 Run()。 相反,使用 base::DoNothing():
MyFunction(base::DoNothing()); // Can be Run(), will no-op
base::DoNothing() 可以为返回 void 的任何 OnceCallback 或 RepeatingCallback 传递。
在实现方面,base::DoNothing() 实际上是一个从 operator() 产生回调的函子。 这使得它在尝试将其他参数绑定到它时无法使用。 通常,将参数绑定到 DoNothing() 的唯一原因是管理对象的生命周期,在这些情况下,您应该努力使用 DeleteSoon()、ReleaseSoon() 或 RefCountedDeleteOnSequence 等惯用语。 如果确实需要将参数绑定到 DoNothing(),或者需要显式创建回调对象(因为通过 operator()() 进行的隐式转换不会编译),则可以直接实例化:
// Binds |foo_ptr| to a no-op OnceCallback takes a scoped_refptr<Foo>. // ANTIPATTERN WARNING: This should likely be changed to ReleaseSoon()! base::Bind(base::DoNothing::Once<scoped_refptr<Foo>>(), foo_ptr);
传递未绑定的输入参数
未绑定参数在回调为 Run() 时指定。 它们在 base::Callback 模板类型中指定:
void MyFunc(int i, const std::string& str) {} base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc); cb.Run(23, "hello, world");
传递绑定输入参数
绑定参数在创建回调时指定为 base::Bind() 的参数。 它们将被传递给函数,回调的 Run() 看不到这些值,甚至不知道它正在调用的函数。
void MyFunc(int i, const std::string& str) {} base::Callback<void()> cb = base::Bind(&MyFunc, 23, "hello world"); cb.Run();
没有未绑定输入参数的回调 (base::Callback<void()>) 称为 base::Closure。 所以我们也可以这样写:
base::Closure cb = base::Bind(&MyFunc, 23, "hello world");
调用成员函数时,绑定参数紧跟在对象指针之后。
base::Closure cb = base::Bind(&MyClass::MyFunc, this, 23, "hello world");
参数的部分绑定
您可以在创建回调时指定一些参数,并在执行回调时指定其余参数。
调用函数时,首先是绑定参数,然后是未绑定参数。
void ReadIntFromFile(const std::string& filename, base::OnceCallback<void(int)> on_read); void DisplayIntWithPrefix(const std::string& prefix, int result) { LOG(INFO) << prefix << result; } void AnotherFunc(const std::string& file) { ReadIntFromFile(file, base::BindOnce(&DisplayIntWithPrefix, "MyPrefix: ")); };
这种技术被称为部分应用。 应该使用它来代替创建包含绑定参数的适配器类。 还要注意“MyPrefix:”参数实际上是一个 const char*,而 DisplayIntWithPrefix 实际上需要一个 const std::string&。 与普通函数调度一样,base::Bind 将尽可能强制参数类型。
使用回调参数避免复制
如果将 base::BindRepeating() 或 base::BindOnce() 的参数作为右值传递,则将其移动到其内部存储中。
std::vector<int> v = {1, 2, 3}; // |v| is moved into the internal storage without copy. base::Bind(&Foo, std::move(v));
// The vector is moved into the internal storage without copy. base::Bind(&Foo, std::vector<int>({1, 2, 3}));
如果可能,总是将与 base::BindOnce() 绑定的参数移动到目标函数。 按值传递并具有移动构造函数的函数参数将被移动而不是复制。 这使得通过 base::BindOnce() 使用仅移动类型变得容易。
相反,与 base::BindRepeating() 绑定的参数只有在参数与 base::Passed() 绑定时才会移动到目标函数。
危险:如果参数与 base::Passed() 绑定,则 base::RepeatingCallback 只能运行一次。 因此,请避免使用 base::Passed()。 如果您知道回调只会被调用一次,则更愿意重构代码以使用 base::OnceCallback。
避免将 base::Passed() 与 base::BindOnce() 一起使用,因为 std::move() 做同样的事情并且更熟悉。
void Foo(std::unique_ptr<int>) {} auto p = std::make_unique<int>(42); // |p| is moved into the internal storage of Bind(), and moved out to |Foo|. base::BindOnce(&Foo, std::move(p)); base::BindRepeating(&Foo, base::Passed(&p)); // Ok, but subtle. base::BindRepeating(&Foo, base::Passed(std::move(p))); // Ok, but subtle.
高级绑定快速参考
用弱指针绑定类方法
如果 MyClass 有一个 base::WeakPtr<MyClass> weak_this_ 成员(见下文),那么可以绑定一个类方法:
base::Bind(&MyClass::Foo, weak_this_);
如果对象已经被销毁,回调将不会运行。
请注意,绑定到 base::WeakPtrs 的类方法回调只能在对象将被销毁的同一序列上运行,否则回调的执行可能会与对象的删除竞争。
要将 base::WeakPtr 与 base::Bind() 一起使用,MyClass 通常如下所示:
class MyClass { public: MyClass() { weak_this_ = weak_factory_.GetWeakPtr(); } private: base::WeakPtr<MyClass> weak_this_; // MyClass member variables go here. base::WeakPtrFactory<MyClass> weak_factory_{this}; };
weak_factory_ 是 MyClass 中的最后一个成员变量,因此它首先被销毁。 这确保了如果绑定到 weak_this_ 的任何类方法在拆卸期间是 Run(),那么它们实际上不会被执行。
如果 MyClass 只使用 base::Bind()s 并在同一序列上执行回调,那么在 base::Bind() 调用中调用weak_factory_.GetWeakPtr() 通常是安全的,而不是在构造期间使用单独的weak_this_。
使用手动生命周期管理绑定类方法
base::Bind(&MyClass::Foo, base::Unretained(this));
这将禁用对象上的所有生命周期管理。 您负责确保对象在调用时是活动的。 你打破它,你拥有它!
绑定一个类方法并让回调拥有该类
MyClass* myclass = new MyClass; base::Bind(&MyClass::Foo, base::Owned(myclass));
当回调被销毁时,该对象将被删除,即使它没有运行(就像你在关机期间发布一个任务)。 对“一劳永逸”的情况可能有用。
还支持智能指针(例如 std::unique_ptr<>)作为接收器。
std::unique_ptr<MyClass> myclass(new MyClass); base::Bind(&MyClass::Foo, std::move(myclass));
忽略返回值
有时您想调用一个函数,该函数在不期望返回值的回调中返回一个值。
int DoSomething(int arg) { cout << arg << endl; } base::RepeatingCallback<void(int)> cb = base::BindRepeating(IgnoreResult(&DoSomething));
类似地,您可能希望使用现有的回调,该回调在需要 void 返回类型的地方返回一个值。
base::RepeatingCallback<int()> cb = base::BindRepeating([](){ return 5; }); base::RepeatingClosure void_cb = base::BindRepeating(base::IgnoreResult(cb));
绑定参数到 Bind() 的快速参考
绑定参数被指定为 base::Bind() 的参数并传递给函数。 没有参数或没有未绑定参数的回调称为 base::Closure(base::Callback<void()> 和 base::Closure 是同一回事)。
Passing Parameters Owned By The Callback
void Foo(int* arg) { cout << *arg << endl; } int* pn = new int(1); base::Closure foo_callback = base::Bind(&foo, base::Owned(pn));
当回调被销毁时,该参数将被删除,即使它没有运行(就像你在关机期间发布任务一样)。
Passing Parameters As A unique_ptr
void TakesOwnership(std::unique_ptr<Foo> arg) {} auto f = std::make_unique<Foo>(); // f becomes null during the following call. base::OnceClosure cb = base::BindOnce(&TakesOwnership, std::move(f));
参数的所有权将与回调一起,直到回调运行,然后所有权传递给回调函数。 这意味着回调只能运行一次。 如果回调从未运行,它将在对象被销毁时删除它。
Passing Parameters As A scoped_refptr
void TakesOneRef(scoped_refptr<Foo> arg) {} scoped_refptr<Foo> f(new Foo); base::Closure cb = base::Bind(&TakesOneRef, f);
这应该“正常工作”。 只要闭包还活着,它就会引用一个引用,而另一个引用将被调用的函数引用。
void DontTakeRef(Foo* arg) {} scoped_refptr<Foo> f(new Foo); base::Closure cb = base::Bind(&DontTakeRef, base::RetainedRef(f));
Passing Parameters By Reference
References are copied unless std::ref
or std::cref
is used. Example:
void foo(const int& arg) { printf("%d %p\n", arg, &arg); } int n = 1; base::Closure has_copy = base::Bind(&foo, n); base::Closure has_ref = base::Bind(&foo, std::cref(n)); n = 2; foo(n); // Prints "2 0xaaaaaaaaaaaa" has_copy.Run(); // Prints "1 0xbbbbbbbbbbbb" has_ref.Run(); // Prints "2 0xaaaaaaaaaaaa"
实施说明
这个设计来自哪里:
base::Callback 和 base::Bind 的设计深受 C++ 的 tr1::function / tr1::bind 以及 Google 内部使用的“Google Callback”系统的影响。
自定义行为
有几个注入点可以从其实现之外控制绑定行为。
namespace base { template <typename Receiver> struct IsWeakReceiver { static constexpr bool value = false; }; template <typename Obj> struct UnwrapTraits { template <typename T> T&& Unwrap(T&& obj) { return std::forward<T>(obj); } }; } // namespace base
如果 base::IsWeakReceiver<Receiver>::value 在方法的接收者上为 true,base::Bind 检查接收者是否被评估为 true,如果评估为 false,则取消调用。 您可以专门化 base::IsWeakReceiver 以将外部智能指针作为弱指针。
在 base::Callback 调用目标函数之前,为每个绑定参数调用 base::UnwrapTraits<BoundObject>::Unwrap()。 你可以专门定义一个参数包装器,例如 base::Unretained、base::Owned、base::RetainedRef 和 base::Passed。
How The Implementation Works:
There are three main components to the system:
- The
base::Callback<>
classes. - The
base::Bind()
functions. - The arguments wrappers (e.g.,
base::Unretained()
andbase::Owned()
).
Callback 类代表一个通用函数指针。
在内部,它存储了一个表示目标函数及其所有绑定参数的引用状态。 base::Callback 构造函数采用 base::BindStateBase*,它是从 base::BindState<> 向上转换的。 在构造函数的上下文中,这个 base::BindState<> 指针的静态类型唯一地标识了它所代表的函数、它的所有绑定参数以及能够调用目标的 Run() 方法。
base::Bind() 创建具有完整静态类型的 base::BindState<>,并擦除目标函数类型以及绑定参数的类型。 它通过存储指向特定 Run() 函数的指针并将 base::BindState<>* 的状态向上转换为 base::BindStateBase* 来实现这一点。 只要此 BindStateBase 指针仅与存储的 Run() 指针一起使用,这是安全的。
要在 base::BindState<> 函数中创建 base::BindState<> 对象。 这些函数,连同一组内部模板,负责
-
将函数签名解包为返回类型和参数 确定绑定的参数数量 创建存储绑定参数的 BindState 执行编译时断言以避免容易出错的行为 返回一个 Callback<> ,其数量与未绑定参数的数量相匹配,并且如果我们绑定一个方法,它知道目标对象的正确引用计数语义。
base::Bind 函数使用类型推断和可变参数模板执行上述操作。
默认情况下,base::Bind() 将存储所有绑定参数的副本,如果被绑定的函数是类方法,则尝试引用目标对象。 即使函数将参数作为 const 引用,也会创建这些副本。 (禁止绑定到非常量引用,请参阅 bind.h。)
为了改变这种行为,我们引入了一组参数包装器(例如,base::Unretained())。 这些是按值传递的简单容器模板,并包装了指向参数的指针。 每个助手在 base/bind.h 中都有一个描述它的注释。
这些类型被传递给 Unwrap() 函数以修改 base::Bind() 的行为。 Unwrap() 函数通过根据参数是否为包装器类型进行部分特化来改变行为。
base::Unretained() 特定于 Chromium。
Missing Functionality
- 将数组绑定到采用非常量指针的函数。 例子:
void Foo(const char* ptr);
void Bar(char* ptr);
base::Bind(&Foo, "test");
base::Bind(&Bar, "test"); // This fails because ptr is not const.
- 在部分绑定参数的情况下,可能在绑定参数之前有未绑定的参数。 例子:
void Foo(int x, bool y);
base::Bind(&Foo, _1, false); // _1 is a placeholder.
如果您正在考虑在自己的头文件中前向声明 base::Callback,请改为包含“base/callback_forward.h”。