From: Philipp Unterbrunner Date: Wed, 1 Mar 2017 06:14:32 +0000 (-0800) Subject: New C++17 backport: folly::enable_shared_from_this X-Git-Tag: v2017.03.06.00~11 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=512aa17590988db15d6f303528a45dfdcb9503de;p=folly.git New C++17 backport: folly::enable_shared_from_this Summary: C++14 has no direct means of creating a std::weak_ptr, one must always create a (temporary) std::shared_ptr first. C++17 adds weak_from_this() to std::enable_shared_from_this to avoid that overhead. Alas code that is meant to compile under different language versions cannot rely on std::enable_shared_from_this::weak_from_this(). This new utility class uses SFINAE to call std::enable_shared_from_this::weak_from_this() if available. Falls back to std::enable_shared_from_this::shared_from_this() otherwise. Use as a drop-in replacement for std::enable_shared_from_this where weak_from_this() is desired. Reviewed By: yfeldblum Differential Revision: D4616394 fbshipit-source-id: 73bf6cd8852d4a33478a9280cc69c81e7ea73423 --- diff --git a/folly/Memory.h b/folly/Memory.h index d4978d92..d2442219 100644 --- a/folly/Memory.h +++ b/folly/Memory.h @@ -446,4 +446,93 @@ std::shared_ptr allocate_shared(Allocator&& allocator, Args&&... args) { */ template struct IsArenaAllocator : std::false_type { }; +/* + * folly::enable_shared_from_this + * + * To be removed once C++17 becomes a minimum requirement for folly. + */ +#if __cplusplus >= 201700L || \ + __cpp_lib_enable_shared_from_this >= 201603L + +// Guaranteed to have std::enable_shared_from_this::weak_from_this(). Prefer +// type alias over our own class. +/* using override */ using std::enable_shared_from_this; + +#else + +/** + * Extends std::enabled_shared_from_this. Offers weak_from_this() to pre-C++17 + * code. Use as drop-in replacement for std::enable_shared_from_this. + * + * C++14 has no direct means of creating a std::weak_ptr, one must always + * create a (temporary) std::shared_ptr first. C++17 adds weak_from_this() to + * std::enable_shared_from_this to avoid that overhead. Alas code that must + * compile under different language versions cannot call + * std::enable_shared_from_this::weak_from_this() directly. Hence this class. + * + * @example + * class MyClass : public folly::enable_shared_from_this {}; + * + * int main() { + * std::shared_ptr sp = std::make_shared(); + * std::weak_ptr wp = sp->weak_from_this(); + * } + */ +template +class enable_shared_from_this : public std::enable_shared_from_this { +public: + constexpr enable_shared_from_this() noexcept = default; + + std::weak_ptr weak_from_this() noexcept { + return weak_from_this_(this); + } + + std::weak_ptr weak_from_this() const noexcept { + return weak_from_this_(this); + } + +private: + // Uses SFINAE to detect and call + // std::enable_shared_from_this::weak_from_this() if available. Falls + // back to std::enable_shared_from_this::shared_from_this() otherwise. + template + auto weak_from_this_(std::enable_shared_from_this* base_ptr) + noexcept -> decltype(base_ptr->weak_from_this()) { + return base_ptr->weak_from_this(); + } + + template + auto weak_from_this_(std::enable_shared_from_this const* base_ptr) + const noexcept -> decltype(base_ptr->weak_from_this()) { + return base_ptr->weak_from_this(); + } + + template + std::weak_ptr weak_from_this_(...) noexcept { + try { + return this->shared_from_this(); + } catch (std::bad_weak_ptr const&) { + // C++17 requires that weak_from_this() on an object not owned by a + // shared_ptr returns an empty weak_ptr. Sadly, in C++14, + // shared_from_this() on such an object is undefined behavior, and there + // is nothing we can do to detect and handle the situation in a portable + // manner. But in case a compiler is nice enough to implement C++17 + // semantics of shared_from_this() and throws a bad_weak_ptr, we catch it + // and return an empty weak_ptr. + return std::weak_ptr{}; + } + } + + template + std::weak_ptr weak_from_this_(...) const noexcept { + try { + return this->shared_from_this(); + } catch (std::bad_weak_ptr const&) { + return std::weak_ptr{}; + } + } +}; + +#endif + } // namespace folly diff --git a/folly/test/MemoryTest.cpp b/folly/test/MemoryTest.cpp index 3ea0a44a..f5df99fb 100644 --- a/folly/test/MemoryTest.cpp +++ b/folly/test/MemoryTest.cpp @@ -22,6 +22,7 @@ #include #include +#include using namespace folly; @@ -105,3 +106,48 @@ TEST(rebind_allocator, sanity_check) { s.reset(); ASSERT_EQ(nullptr, s.get()); } + +template +static void test_enable_shared_from_this(std::shared_ptr sp) { + ASSERT_EQ(1l, sp.use_count()); + + // Test shared_from_this(). + std::shared_ptr sp2 = sp->shared_from_this(); + ASSERT_EQ(sp, sp2); + + // Test weak_from_this(). + std::weak_ptr wp = sp->weak_from_this(); + ASSERT_EQ(sp, wp.lock()); + sp.reset(); + sp2.reset(); + ASSERT_EQ(nullptr, wp.lock()); + + // Test shared_from_this() and weak_from_this() on object not owned by a + // shared_ptr. Undefined in C++14 but well-defined in C++17. Also known to + // work with libstdc++ >= 20150123. Feel free to add other standard library + // versions where the behavior is known. +#if __cplusplus >= 201700L || \ + __GLIBCXX__ >= 20150123L + C stack_resident; + ASSERT_THROW(stack_resident.shared_from_this(), std::bad_weak_ptr); + ASSERT_TRUE(stack_resident.weak_from_this().expired()); +#endif +} + +TEST(enable_shared_from_this, compatible_with_std_enable_shared_from_this) { + // Compile-time compatibility. + class C_std : public std::enable_shared_from_this {}; + class C_folly : public folly::enable_shared_from_this {}; + static_assert( + noexcept(std::declval().shared_from_this()) == + noexcept(std::declval().shared_from_this()), ""); + static_assert( + noexcept(std::declval().shared_from_this()) == + noexcept(std::declval().shared_from_this()), ""); + static_assert(noexcept(std::declval().weak_from_this()), ""); + static_assert(noexcept(std::declval().weak_from_this()), ""); + + // Runtime compatibility. + test_enable_shared_from_this(std::make_shared()); + test_enable_shared_from_this(std::make_shared()); +}