diff options
author | Magnus Jedvert <magjed@webrtc.org> | 2015-08-20 16:03:52 +0200 |
---|---|---|
committer | Magnus Jedvert <magjed@webrtc.org> | 2015-08-20 14:04:01 +0000 |
commit | d3de9c548d1121e7c4787a4b81fd66be714abc04 (patch) | |
tree | 67e580a50ff6e8c901426ab03a1ec1df4ca6175e | |
parent | efefda6062f8bf844cc7a01c80c26461c834c5e7 (diff) | |
download | webrtc-d3de9c548d1121e7c4787a4b81fd66be714abc04.tar.gz |
rtc::Bind: Capture method objects as scoped_refptr if they are ref counted
R=tommi@webrtc.org
Review URL: https://codereview.webrtc.org/1300523004 .
Cr-Commit-Position: refs/heads/master@{#9744}
-rw-r--r-- | webrtc/base/bind.h | 213 | ||||
-rw-r--r-- | webrtc/base/bind.h.pump | 101 | ||||
-rw-r--r-- | webrtc/base/bind_unittest.cc | 102 |
3 files changed, 396 insertions, 20 deletions
diff --git a/webrtc/base/bind.h b/webrtc/base/bind.h index ebd395979c..2d8114052d 100644 --- a/webrtc/base/bind.h +++ b/webrtc/base/bind.h @@ -16,12 +16,13 @@ // /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump // Bind() is an overloaded function that converts method calls into function -// objects (aka functors). It captures any arguments to the method by value -// when Bind is called, producing a stateful, nullary function object. Care -// should be taken about the lifetime of objects captured by Bind(); the -// returned functor knows nothing about the lifetime of the method's object or -// any arguments passed by pointer, and calling the functor with a destroyed -// object will surely do bad things. +// objects (aka functors). The method object is captured as a scoped_refptr<> if +// possible, and as a raw pointer otherwise. Any arguments to the method are +// captured by value. The return value of Bind is a stateful, nullary function +// object. Care should be taken about the lifetime of objects captured by +// Bind(); the returned functor knows nothing about the lifetime of a non +// ref-counted method object or any arguments passed by pointer, and calling the +// functor with a destroyed object will surely do bad things. // // Example usage: // struct Foo { @@ -38,10 +39,33 @@ // cout << rtc::Bind(&Foo::Test3, &foo, 3)() << endl; // cout << rtc::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl; // } +// +// Example usage of ref counted objects: +// struct Bar { +// int AddRef(); +// int Release(); +// +// void Test() {} +// void BindThis() { +// // The functor passed to AsyncInvoke() will keep this object alive. +// invoker.AsyncInvoke(rtc::Bind(&Bar::Test, this)); +// } +// }; +// +// int main() { +// rtc::scoped_refptr<Bar> bar = new rtc::RefCountedObject<Bar>(); +// auto functor = rtc::Bind(&Bar::Test, bar); +// bar = nullptr; +// // The functor stores an internal scoped_refptr<Bar>, so this is safe. +// functor(); +// } +// #ifndef WEBRTC_BASE_BIND_H_ #define WEBRTC_BASE_BIND_H_ +#include "webrtc/base/scoped_ref_ptr.h" + #define NONAME namespace rtc { @@ -53,6 +77,57 @@ namespace detail { // references stripped. This trick allows the compiler to dictate the Bind // parameter types rather than deduce them. template <class T> struct identity { typedef T type; }; + +// IsRefCounted<T>::value will be true for types that can be used in +// rtc::scoped_refptr<T>, i.e. types that implements nullary functions AddRef() +// and Release(), regardless of their return types. AddRef() and Release() can +// be defined in T or any superclass of T. +template <typename T> +class IsRefCounted { + // This is a complex implementation detail done with SFINAE. + + // Define types such that sizeof(Yes) != sizeof(No). + struct Yes { char dummy[1]; }; + struct No { char dummy[2]; }; + // Define two overloaded template functions with return types of different + // size. This way, we can use sizeof() on the return type to determine which + // function the compiler would have chosen. One function will be preferred + // over the other if it is possible to create it without compiler errors, + // otherwise the compiler will simply remove it, and default to the less + // preferred function. + template <typename R> + static Yes test(R* r, decltype(r->AddRef(), r->Release(), 42)); + template <typename C> static No test(...); + +public: + // Trick the compiler to tell if it's possible to call AddRef() and Release(). + static const bool value = sizeof(test<T>((T*)nullptr, 42)) == sizeof(Yes); +}; + +// TernaryTypeOperator is a helper class to select a type based on a static bool +// value. +template <bool condition, typename IfTrueT, typename IfFalseT> +struct TernaryTypeOperator {}; + +template <typename IfTrueT, typename IfFalseT> +struct TernaryTypeOperator<true, IfTrueT, IfFalseT> { + typedef IfTrueT type; +}; + +template <typename IfTrueT, typename IfFalseT> +struct TernaryTypeOperator<false, IfTrueT, IfFalseT> { + typedef IfFalseT type; +}; + +// PointerType<T>::type will be scoped_refptr<T> for ref counted types, and T* +// otherwise. +template <class T> +struct PointerType { + typedef typename TernaryTypeOperator<IsRefCounted<T>::value, + scoped_refptr<T>, + T*>::type type; +}; + } // namespace detail template <class ObjectT, class MethodT, class R> @@ -64,7 +139,7 @@ class MethodFunctor0 { return (object_->*method_)(); } private: MethodT method_; - ObjectT* object_; + typename detail::PointerType<ObjectT>::type object_; }; template <class FunctorT, class R> @@ -99,6 +174,16 @@ Bind(FP_T(method), const ObjectT* object) { } #undef FP_T +#define FP_T(x) R (ObjectT::*x)() + +template <class ObjectT, class R> +MethodFunctor0<ObjectT, FP_T(NONAME), R> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object) { + return MethodFunctor0<ObjectT, FP_T(NONAME), R>( + method, object.get()); +} + +#undef FP_T #define FP_T(x) R (*x)() template <class R> @@ -122,7 +207,7 @@ class MethodFunctor1 { return (object_->*method_)(p1_); } private: MethodT method_; - ObjectT* object_; + typename detail::PointerType<ObjectT>::type object_; P1 p1_; }; @@ -165,6 +250,18 @@ Bind(FP_T(method), const ObjectT* object, } #undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1) + +template <class ObjectT, class R, + class P1> +MethodFunctor1<ObjectT, FP_T(NONAME), R, P1> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object, + typename detail::identity<P1>::type p1) { + return MethodFunctor1<ObjectT, FP_T(NONAME), R, P1>( + method, object.get(), p1); +} + +#undef FP_T #define FP_T(x) R (*x)(P1) template <class R, @@ -193,7 +290,7 @@ class MethodFunctor2 { return (object_->*method_)(p1_, p2_); } private: MethodT method_; - ObjectT* object_; + typename detail::PointerType<ObjectT>::type object_; P1 p1_; P2 p2_; }; @@ -244,6 +341,20 @@ Bind(FP_T(method), const ObjectT* object, } #undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2) + +template <class ObjectT, class R, + class P1, + class P2> +MethodFunctor2<ObjectT, FP_T(NONAME), R, P1, P2> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object, + typename detail::identity<P1>::type p1, + typename detail::identity<P2>::type p2) { + return MethodFunctor2<ObjectT, FP_T(NONAME), R, P1, P2>( + method, object.get(), p1, p2); +} + +#undef FP_T #define FP_T(x) R (*x)(P1, P2) template <class R, @@ -277,7 +388,7 @@ class MethodFunctor3 { return (object_->*method_)(p1_, p2_, p3_); } private: MethodT method_; - ObjectT* object_; + typename detail::PointerType<ObjectT>::type object_; P1 p1_; P2 p2_; P3 p3_; @@ -336,6 +447,22 @@ Bind(FP_T(method), const ObjectT* object, } #undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3) + +template <class ObjectT, class R, + class P1, + class P2, + class P3> +MethodFunctor3<ObjectT, FP_T(NONAME), R, P1, P2, P3> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object, + typename detail::identity<P1>::type p1, + typename detail::identity<P2>::type p2, + typename detail::identity<P3>::type p3) { + return MethodFunctor3<ObjectT, FP_T(NONAME), R, P1, P2, P3>( + method, object.get(), p1, p2, p3); +} + +#undef FP_T #define FP_T(x) R (*x)(P1, P2, P3) template <class R, @@ -374,7 +501,7 @@ class MethodFunctor4 { return (object_->*method_)(p1_, p2_, p3_, p4_); } private: MethodT method_; - ObjectT* object_; + typename detail::PointerType<ObjectT>::type object_; P1 p1_; P2 p2_; P3 p3_; @@ -441,6 +568,24 @@ Bind(FP_T(method), const ObjectT* object, } #undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4) + +template <class ObjectT, class R, + class P1, + class P2, + class P3, + class P4> +MethodFunctor4<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object, + typename detail::identity<P1>::type p1, + typename detail::identity<P2>::type p2, + typename detail::identity<P3>::type p3, + typename detail::identity<P4>::type p4) { + return MethodFunctor4<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4>( + method, object.get(), p1, p2, p3, p4); +} + +#undef FP_T #define FP_T(x) R (*x)(P1, P2, P3, P4) template <class R, @@ -484,7 +629,7 @@ class MethodFunctor5 { return (object_->*method_)(p1_, p2_, p3_, p4_, p5_); } private: MethodT method_; - ObjectT* object_; + typename detail::PointerType<ObjectT>::type object_; P1 p1_; P2 p2_; P3 p3_; @@ -559,6 +704,26 @@ Bind(FP_T(method), const ObjectT* object, } #undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5) + +template <class ObjectT, class R, + class P1, + class P2, + class P3, + class P4, + class P5> +MethodFunctor5<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object, + typename detail::identity<P1>::type p1, + typename detail::identity<P2>::type p2, + typename detail::identity<P3>::type p3, + typename detail::identity<P4>::type p4, + typename detail::identity<P5>::type p5) { + return MethodFunctor5<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5>( + method, object.get(), p1, p2, p3, p4, p5); +} + +#undef FP_T #define FP_T(x) R (*x)(P1, P2, P3, P4, P5) template <class R, @@ -607,7 +772,7 @@ class MethodFunctor6 { return (object_->*method_)(p1_, p2_, p3_, p4_, p5_, p6_); } private: MethodT method_; - ObjectT* object_; + typename detail::PointerType<ObjectT>::type object_; P1 p1_; P2 p2_; P3 p3_; @@ -690,6 +855,28 @@ Bind(FP_T(method), const ObjectT* object, } #undef FP_T +#define FP_T(x) R (ObjectT::*x)(P1, P2, P3, P4, P5, P6) + +template <class ObjectT, class R, + class P1, + class P2, + class P3, + class P4, + class P5, + class P6> +MethodFunctor6<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5, P6> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object, + typename detail::identity<P1>::type p1, + typename detail::identity<P2>::type p2, + typename detail::identity<P3>::type p3, + typename detail::identity<P4>::type p4, + typename detail::identity<P5>::type p5, + typename detail::identity<P6>::type p6) { + return MethodFunctor6<ObjectT, FP_T(NONAME), R, P1, P2, P3, P4, P5, P6>( + method, object.get(), p1, p2, p3, p4, p5, p6); +} + +#undef FP_T #define FP_T(x) R (*x)(P1, P2, P3, P4, P5, P6) template <class R, diff --git a/webrtc/base/bind.h.pump b/webrtc/base/bind.h.pump index 11767abe50..9a4bc664c3 100644 --- a/webrtc/base/bind.h.pump +++ b/webrtc/base/bind.h.pump @@ -12,12 +12,13 @@ // /home/build/google3/third_party/gtest/scripts/pump.py bind.h.pump // Bind() is an overloaded function that converts method calls into function -// objects (aka functors). It captures any arguments to the method by value -// when Bind is called, producing a stateful, nullary function object. Care -// should be taken about the lifetime of objects captured by Bind(); the -// returned functor knows nothing about the lifetime of the method's object or -// any arguments passed by pointer, and calling the functor with a destroyed -// object will surely do bad things. +// objects (aka functors). The method object is captured as a scoped_refptr<> if +// possible, and as a raw pointer otherwise. Any arguments to the method are +// captured by value. The return value of Bind is a stateful, nullary function +// object. Care should be taken about the lifetime of objects captured by +// Bind(); the returned functor knows nothing about the lifetime of a non +// ref-counted method object or any arguments passed by pointer, and calling the +// functor with a destroyed object will surely do bad things. // // Example usage: // struct Foo { @@ -34,10 +35,33 @@ // cout << rtc::Bind(&Foo::Test3, &foo, 3)() << endl; // cout << rtc::Bind(&Foo::Test4, &foo, 7, 8.5f)() << endl; // } +// +// Example usage of ref counted objects: +// struct Bar { +// int AddRef(); +// int Release(); +// +// void Test() {} +// void BindThis() { +// // The functor passed to AsyncInvoke() will keep this object alive. +// invoker.AsyncInvoke(rtc::Bind(&Bar::Test, this)); +// } +// }; +// +// int main() { +// rtc::scoped_refptr<Bar> bar = new rtc::RefCountedObject<Bar>(); +// auto functor = rtc::Bind(&Bar::Test, bar); +// bar = nullptr; +// // The functor stores an internal scoped_refptr<Bar>, so this is safe. +// functor(); +// } +// #ifndef WEBRTC_BASE_BIND_H_ #define WEBRTC_BASE_BIND_H_ +#include "webrtc/base/scoped_ref_ptr.h" + #define NONAME namespace rtc { @@ -49,6 +73,57 @@ namespace detail { // references stripped. This trick allows the compiler to dictate the Bind // parameter types rather than deduce them. template <class T> struct identity { typedef T type; }; + +// IsRefCounted<T>::value will be true for types that can be used in +// rtc::scoped_refptr<T>, i.e. types that implements nullary functions AddRef() +// and Release(), regardless of their return types. AddRef() and Release() can +// be defined in T or any superclass of T. +template <typename T> +class IsRefCounted { + // This is a complex implementation detail done with SFINAE. + + // Define types such that sizeof(Yes) != sizeof(No). + struct Yes { char dummy[1]; }; + struct No { char dummy[2]; }; + // Define two overloaded template functions with return types of different + // size. This way, we can use sizeof() on the return type to determine which + // function the compiler would have chosen. One function will be preferred + // over the other if it is possible to create it without compiler errors, + // otherwise the compiler will simply remove it, and default to the less + // preferred function. + template <typename R> + static Yes test(R* r, decltype(r->AddRef(), r->Release(), 42)); + template <typename C> static No test(...); + +public: + // Trick the compiler to tell if it's possible to call AddRef() and Release(). + static const bool value = sizeof(test<T>((T*)nullptr, 42)) == sizeof(Yes); +}; + +// TernaryTypeOperator is a helper class to select a type based on a static bool +// value. +template <bool condition, typename IfTrueT, typename IfFalseT> +struct TernaryTypeOperator {}; + +template <typename IfTrueT, typename IfFalseT> +struct TernaryTypeOperator<true, IfTrueT, IfFalseT> { + typedef IfTrueT type; +}; + +template <typename IfTrueT, typename IfFalseT> +struct TernaryTypeOperator<false, IfTrueT, IfFalseT> { + typedef IfFalseT type; +}; + +// PointerType<T>::type will be scoped_refptr<T> for ref counted types, and T* +// otherwise. +template <class T> +struct PointerType { + typedef typename TernaryTypeOperator<IsRefCounted<T>::value, + scoped_refptr<T>, + T*>::type type; +}; + } // namespace detail $var n = 6 @@ -68,7 +143,7 @@ class MethodFunctor$i { return (object_->*method_)($for j , [[p$(j)_]]); } private: MethodT method_; - ObjectT* object_;$for j [[ + typename detail::PointerType<ObjectT>::type object_;$for j [[ P$j p$(j)_;]] @@ -116,6 +191,18 @@ Bind(FP_T(method), const ObjectT* object$for j [[, } #undef FP_T +#define FP_T(x) R (ObjectT::*x)($for j , [[P$j]]) + +template <class ObjectT, class R$for j [[, + class P$j]]> +MethodFunctor$i<ObjectT, FP_T(NONAME), R$for j [[, P$j]]> +Bind(FP_T(method), const scoped_refptr<ObjectT>& object$for j [[, + typename detail::identity<P$j>::type p$j]]) { + return MethodFunctor$i<ObjectT, FP_T(NONAME), R$for j [[, P$j]]>( + method, object.get()$for j [[, p$j]]); +} + +#undef FP_T #define FP_T(x) R (*x)($for j , [[P$j]]) template <class R$for j [[, diff --git a/webrtc/base/bind_unittest.cc b/webrtc/base/bind_unittest.cc index ed8dd5cf2d..7a621dce2c 100644 --- a/webrtc/base/bind_unittest.cc +++ b/webrtc/base/bind_unittest.cc @@ -11,6 +11,8 @@ #include "webrtc/base/bind.h" #include "webrtc/base/gunit.h" +#include "webrtc/base/refcount.h" + namespace rtc { namespace { @@ -26,12 +28,67 @@ struct MethodBindTester { mutable int call_count; }; +struct A { int dummy; }; +struct B: public RefCountInterface { int dummy; }; +struct C: public A, B {}; +struct D { + int AddRef(); +}; +struct E: public D { + int Release(); +}; +struct F { + void AddRef(); + void Release(); +}; + +class LifeTimeCheck : public RefCountInterface { + public: + LifeTimeCheck(bool* has_died) : has_died_(has_died), is_ok_to_die_(false) {} + ~LifeTimeCheck() { + EXPECT_TRUE(is_ok_to_die_); + *has_died_ = true; + } + void PrepareToDie() { is_ok_to_die_ = true; } + void NullaryVoid() {} + + private: + bool* const has_died_; + bool is_ok_to_die_; +}; + int Return42() { return 42; } int Negate(int a) { return -a; } int Multiply(int a, int b) { return a * b; } } // namespace +// Try to catch any problem with scoped_refptr type deduction in rtc::Bind at +// compile time. +#define EXPECT_IS_CAPTURED_AS_PTR(T) \ + static_assert(is_same<detail::PointerType<T>::type, T*>::value, \ + "PointerType") +#define EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(T) \ + static_assert( \ + is_same<detail::PointerType<T>::type, scoped_refptr<T>>::value, \ + "PointerType") + +EXPECT_IS_CAPTURED_AS_PTR(void); +EXPECT_IS_CAPTURED_AS_PTR(int); +EXPECT_IS_CAPTURED_AS_PTR(double); +EXPECT_IS_CAPTURED_AS_PTR(A); +EXPECT_IS_CAPTURED_AS_PTR(D); +EXPECT_IS_CAPTURED_AS_PTR(RefCountInterface*); + +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(RefCountInterface); +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(B); +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(C); +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(E); +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(F); +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(RefCountedObject<RefCountInterface>); +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(RefCountedObject<B>); +EXPECT_IS_CAPTURED_AS_SCOPED_REFPTR(RefCountedObject<C>); + TEST(BindTest, BindToMethod) { MethodBindTester object = {0}; EXPECT_EQ(0, object.call_count); @@ -64,4 +121,49 @@ TEST(BindTest, BindToFunction) { EXPECT_EQ(56, Bind(&Multiply, 8, 7)()); } +// Test Bind where method object implements RefCountInterface and is passed as a +// pointer. +TEST(BindTest, CapturePointerAsScopedRefPtr) { + bool object_has_died = false; + scoped_refptr<LifeTimeCheck> object = + new RefCountedObject<LifeTimeCheck>(&object_has_died); + { + auto functor = Bind(&LifeTimeCheck::PrepareToDie, object.get()); + object = nullptr; + EXPECT_FALSE(object_has_died); + // Run prepare to die via functor. + functor(); + } + EXPECT_TRUE(object_has_died); +} + +// Test Bind where method object implements RefCountInterface and is passed as a +// scoped_refptr<>. +TEST(BindTest, CaptureScopedRefPtrAsScopedRefPtr) { + bool object_has_died = false; + scoped_refptr<LifeTimeCheck> object = + new RefCountedObject<LifeTimeCheck>(&object_has_died); + { + auto functor = Bind(&LifeTimeCheck::PrepareToDie, object); + object = nullptr; + EXPECT_FALSE(object_has_died); + // Run prepare to die via functor. + functor(); + } + EXPECT_TRUE(object_has_died); +} + +// Test Bind where method object is captured as scoped_refptr<> and the functor +// dies while there are references left. +TEST(BindTest, FunctorReleasesObjectOnDestruction) { + bool object_has_died = false; + scoped_refptr<LifeTimeCheck> object = + new RefCountedObject<LifeTimeCheck>(&object_has_died); + Bind(&LifeTimeCheck::NullaryVoid, object.get())(); + EXPECT_FALSE(object_has_died); + object->PrepareToDie(); + object = nullptr; + EXPECT_TRUE(object_has_died); +} + } // namespace rtc |