Document of callback.h in mojo



导言
——————
模板回调类是一个广义函数对象。与bind.h中的bind()函数一起,它们提供了一种类型安全的方法来执行函数的部分应用程序。部分应用程序(或“currying”)是将一个函数的参数的子集绑定到生成另一个需要较少参数的函数的过程。这可以用来传递一个延迟执行单元,就像其他语言中使用词法闭包一样。例如,在Chromium代码中使用它来调度不同MessageLoops上的任务。

无未绑定输入参数的回调(base::Callback)称为base::closure。请注意,这与其他语言所称的闭包不同–它不保留对其封闭环境的引用。

内存管理和传递
——————
回调对象本身应该通过常量引用(const-ref)传递,并通过复制进行存储。它们在内部通过一个有引用计数的类存储它们的状态,因此不需要删除。
通过常量引用传递的原因是为了避免不必要的AddRef/Release改变内部状态。

基础材料快速参考
——————
O 绑定普通函数

   int Return5() { return 5; }
   base::Callback<int(void)> func_cb = base::Bind(&Return5);
   LOG(INFO) << func_cb.Run();  // Prints 5.

O 绑定一个类方法

第一个要绑定的参数是要调用的成员函数,第二个参数是要调用它的对象。

   class Ref : public base::RefCountedThreadSafe<Ref> {
    public:
     int Foo() { return 3; }
     void PrintBye() { LOG(INFO) << "bye."; }
   };
   scoped_refptr<Ref> ref = new Ref();
   base::Callback<void(void)> ref_cb = base::Bind(&Ref::Foo, ref);
   LOG(INFO) << ref_cb.Run();  // Prints out 3.

默认情况下,对象必须支持RefCounted,否则将出现编译器错误。如果在线程之间传递,请确保它是RefCountedThreadSafe!如果您不想使用引用计数,请参阅下面的“成员函数的高级绑定”。

O 运行回调

可以使用其“Run”方法运行回调,该方法具有与回调的模板参数相同的特征。

   void DoSomething(const base::Callback<void(int, std::string)>& callback) {
     callback.Run(5, "hello");
   }

回调可以多次运行(运行时不会删除或标记回调)。但是,使用base::Passed时不行(见下文)。

   void DoSomething(const base::Callback<double(double)>& callback) {
     double myresult = callback.Run(3.14159);
     myresult += callback.Run(2.71828);
   }

O 传递未绑定的输入参数

在Run()运行回调时指定未绑定的参数。它们在回调模板类型中指定:

   void MyFunc(int i, const std::string& str) {}
   base::Callback<void(int, const std::string&)> cb = base::Bind(&MyFunc);
   cb.Run(23, "hello, world");

O 绑定(Bound Parameters)输入参数。
当您将回调创建为BIND()的参数时,将指定绑定参数。
它们将被传递给函数,回调Run()的实际运行者看不到这些值,甚至不知道它正在调用的函数。

   void MyFunc(int i, const std::string& str) {}
   base::Callback<void(void)> cb = base::Bind(&MyFunc, 23, "hello world");
   cb.Run();

无未绑定输入参数的回调(base::Callback)称为base::closure。所以我们也可以写:

   base::Closure cb = base::Bind(&MyFunc, 23, "hello world");

调用成员函数时,绑定参数跟在对象指针之后。

   base::Closure cb = base::Bind(&MyClass::MyFunc, this, 23, "hello world");

参数的部分绑定。
可以在创建回调时指定一些参数,在执行回调时指定其余参数。

   void MyFunc(int i, const std::string& str) {}
   base::Callback<void(const std::string&)> cb = base::Bind(&MyFunc, 23);
   cb.Run("hello world");

调用函数时,首先是绑定参数,然后是未绑定参数。

高级绑定的快速参考
——————

O 用弱指针绑定类方法

base::Bind(&MyClass::Foo, GetWeakPtr());

如果对象已被销毁,则不会运行回调。
危险:弱指针不是线程安全的,所以在线程之间传递时不要使用它!

O 绑定类方法并手动进行生存期管理

base::Bind(&MyClass::Foo, base::Unretained(this));

这将禁用对象上的所有生存期管理。
您负责确保对象在调用时处于活动状态。

O 绑定一个类方法并让回调拥有这个类

   MyClass* myclass = new MyClass;
   base::Bind(&MyClass::Foo, base::Owned(myclass));

当回调被销毁时,该对象将被删除,即使它没有运行(如在关闭期间发布任务)。可能对“Fire & Forgot”的情况有用。

O 忽略返回值

有时,您希望调用一个函数,不希望该函数在回调中返回一个值。

   int DoSomething(int arg) { cout << arg << endl; }
   base::Callback<void<int>) cb =
       base::Bind(base::IgnoreResult(&DoSomething));

绑定参数到Bind()的快速参考
——————

O 绑定参数被指定为Bind()的参数,并传递给函数

没有参数或没有未绑定参数的回调称为闭包(base::Callback 和base::closure是一样的)

O 传递回调所拥有的参数

   void Foo(int* arg) { cout << *arg << endl; }
   int* pn = new int(1);
   base::Closure foo_callback = base::Bind(&foo, base::Owned(pn));

该参数将在回调被销毁时被删除,即使它未运行(如在关闭期间发布task)。

O 作为作用域_ptr传递参数

   void TakesOwnership(scoped_ptr<Foo> arg) {}
   scoped_ptr<Foo> f(new Foo);
   // f becomes null during the following call.
   base::Closure cb = base::Bind(&TakesOwnership, base::Passed(&f));

参数的所有权将与回调一起使用,直到将所有权传递给回调函数时才会运行它。这意味着回调只能运行一次。如果回调从未运行过,它将在销毁对象时将其删除。

O 将参数作为scpoed_refptr传递

   void TakesOneRef(scoped_refptr<Foo> arg) {}
   scoped_refptr<Foo> f(new Foo)
   base::Closure cb = base::Bind(&TakesOneRef, f);

这应该“就行了”。只要闭包还活着,它就会接受一个引用,被调用函数的另一个引用也会被接受。

O 通过引用传递参数

除非使用ConstRef,否则常量引用将被*复制*。
举例:

   void foo(const int& arg) { printf("%d %pn", arg, &arg); }
   int n = 1;
   base::Closure has_copy = base::Bind(&foo, n);
   base::Closure has_ref = base::Bind(&foo, base::ConstRef(n));
   n = 2;
   foo(n);                        // Prints "2 0xaaaaaaaaaaaa"
   has_copy.Run();                // Prints "1 0xbbbbbbbbbbbb"
   has_ref.Run();                 // Prints "2 0xaaaaaaaaaaaa"

一般情况下,参数会复制到闭包中。危险:ConstRef在这里会存储一个const引用,并引用原始参数。这意味着您必须确保对象在回调完成前仍然存活!

实施说明
——————

此设计来自何处:
设计回调和绑定思想受到C++的tr1::function/tr1::bin和Google内部使用的“Google回调”系统的影响。

实施是如何工作的:
该系统有三个主要组成部分:
1)回调类。
2)Bind()函数。
3)参数wrapper(例如,UnRetainted()和ConstRef())。

回调类表示泛型函数指针。在内部,它存储一段重新计算的状态,表示目标函数及其所有绑定参数。每个特化回调都有一个模板构造函数,它接受一个BindState<>*。
在构造函数的上下文中,此BindState<>*指针的静态类型唯一地标识它所代表的函数、它的所有绑定参数以及能够调用目标的Run()方法。
回调的构造函数接受具有完整静态类型的BindState<>*,并删除目标函数类型以及绑定参数的类型。
为此,它存储一个指向特定Run()函数的指针,并将BindState<>*的状态转换为BindStateBase*。
只要这个BindStateBase*指针只与存储的Run()指针一起使用,就是安全的。

对于BindState<>*,对象是在Bind()函数中创建的。这些函数,以及一组内部模板负责下列工作:
-将函数特征(Function signature)展开为返回类型和参数。
-确定绑定的参数数量。
-创建存储绑定参数的BindState。
-执行编译时断言以避免容易出错的行为。
-返回一个Callback<>,该回调与未绑定参数的数量匹配,并且如果我们绑定一个方法,它知道目标对象的正确的refcounting 语义。

绑定函数使用类型推断和特化模板来完成上述工作。默认情况下,Bind()将存储所有绑定参数的副本,如果绑定的函数是类方法,则尝试refcount 目标对象。即使函数将参数作为常量引用,也会创建这些副本。(禁止绑定到非const引用,请参见bind.h)。

为了改变这一行为,我们引入了一组参数包装器(例如 Unretained(), ConstRef())。这些是通过值传递的简单容器模板,并包装指向参数的指针。
有关更多信息,请参见base/bind_helpass.h中的文件级注释。

这些类型将分别传递给Unwrap()函数和MaybeRefcount()函数以修改Bind()的行为。Unwrap()和MaybeRefcount()函数通过根据参数是否是wrapper类型执行部分特化来更改行为。
ConstRef()类似于tr1::cref。“Unretained”()是Chromium所特有的。

为什么不使用TR1函数/BIND?

曾经考虑过直接使用tr1::function和tr1::bind,但由于在构造期间绑定参数和在调用期间转发参数所涉及的复制构造函数调用的数量而最终被拒绝。
这些拷贝在C++0x中不再是一个问题,因为C++0x将支持rvalue引用,允许编译器避免这些拷贝。但是,等待C++0x不是一个选项。

在GCC版本4.4.3(Ubuntu4.4.3-4ubuntu5)上使用valgrind进行测量时,tr1::bind调用本身将为每个绑定参数调用三次非简单的复制构造函数。

此外,每次传递tr1::function时,都会再次复制每个绑定参数。除了在绑定和调用时获取的副本外,复制tr1::function还会导致复制所有绑定参数和状态。此外,在Chromium中,在表示类方法调用时,回调需要对目标对象进行引用。这不受tr1支持。

最后,tr1::function和tr1::bind有一个更通用、更灵活的API。
这包括使用tr1::bind::placeholder对参数重新排序、对非Const引用参数的支持以及tr1::function对象的一些有限的子类型(例如,tr1::function可转换为tr1::function)。这些不是Chromium所需的功能。

其中的一些,例如允许引用参数和函数的子类型化,实际上可能会成为错误的来源。删除对这些特性的支持实际上允许更简单的实现和更精确的API。

为什么不用Google Callback呢?
Google回调系统也不支持重新计算。此外,在参数的类型转换方面,它的实现有一些奇怪的边缘情况。特别是,参数的恒定性有时必须与函数特征完全匹配,否则类型推断可能会中断。

鉴于上述情况,编写一个定制的解决方案就更容易了。

目前还缺少的功能:
-调用Bind的返回值,即bind(&foo).Run()不起作用;
-将数组绑定到接受非常量指针的函数。
举例:

      void Foo(const char* ptr);
      void Bar(char* ptr);
      Bind(&Foo, "test");
      Bind(&Bar, "test");  // This fails because ptr is not const.

——————————————

头文件声明:

namespace base {

首先,我们向前声明回调类模板。这将通知编译器,模板只有1个类型参数,这是回调所表示的函数特征。之后,为0-7个参数创建特化模板。

注意,即使模板类型列表增加了,特化仍然只有一个类型:函数特征(Function signature)。

如果您正在考虑在您自己的头文件中向前声明回调,请改为包含“base/Callback_Forward.h”。

template <typename Sig>
class Callback;

namespace internal {
template <typename Runnable, typename RunType, typename BoundArgsType>
struct BindState;
}  // namespace internal

template <typename R, typename... Args>
class Callback<R(Args...)> : public internal::CallbackBase {
 public:
  typedef R(RunType)(Args...);

  Callback() : CallbackBase(NULL) { }

请注意,此构造函数不能是显式的,而且bind()不能返回确切的Callback<>类型。
有关详细信息,请参见base/bind.h。

  template <typename Runnable, typename BindRunType, typename BoundArgsType>
  Callback(internal::BindState<Runnable, BindRunType,
           BoundArgsType>* bind_state)
      : CallbackBase(bind_state) {

强制将赋值赋给Polymoric Invoke的局部变量,这样编译器将检查传入的run()方法是否具有正确的类型。

    PolymorphicInvoke invoke_func =
        &internal::BindState<Runnable, BindRunType, BoundArgsType>
            ::InvokerType::Run;
    polymorphic_invoke_ = reinterpret_cast<InvokeFuncStorage>(invoke_func);
  }

  bool Equals(const Callback& other) const {
    return CallbackBase::Equals(other);
  }

  R Run(typename internal::CallbackParamTraits<Args>::ForwardType... args)
      const {
    PolymorphicInvoke f =
        reinterpret_cast<PolymorphicInvoke>(polymorphic_invoke_);

    return f(bind_state_.get(), internal::CallbackForward(args)...);
  }

 private:
  typedef R(*PolymorphicInvoke)(
      internal::BindStateBase*,
      typename internal::CallbackParamTraits<Args>::ForwardType...);
};

语法糖,使Callback更易于声明,因为它将用于许多具有延迟执行的API中。

typedef Callback<void(void)> Closure;

}  // namespace base
Wednesday, March 6, 2019 by blast