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:

  1. The base::Callback<> classes.
  2. The base::Bind() functions.
  3. The arguments wrappers (e.g., base::Unretained() and base::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”。

 

posted @ 2022-06-18 11:25  冰糖葫芦很乖  阅读(987)  评论(0编辑  收藏  举报