Implementing unique/shared_ptr for custom allocators (like Arena) in folly
authorMarcelo Juchem <marcelo@fb.com>
Tue, 8 Jan 2013 03:05:53 +0000 (19:05 -0800)
committerJordan DeLong <jdelong@fb.com>
Mon, 4 Feb 2013 17:25:50 +0000 (09:25 -0800)
Summary:
- moving simple allocator *_ptr and convenience functions to folly
- getting rid of arena_new - it encourages manually constructing smart pointers when folly::allocate_* should be used instead
- using std::allocate_shared to construct shared_ptrs, thus properly using the allocator for the ref-counter too
- fixing forwarding of parameters in the convenience functions
- uniform allocation of smart pointers using both stl and non stl-like allocators

Test Plan:
- build + tests of existing client code
- basic unit tests for arena smart pointers

Reviewed By: delong.j@fb.com

FB internal diff: D672818

folly/StlAllocator.h
folly/Traits.h
folly/test/ArenaSmartPtrTest.cpp [new file with mode: 0644]
folly/test/HasMemberFnTraitsTest.cpp [new file with mode: 0644]

index 3848343d4b47a2dc580fdf6267e37cc17aeb8c2e..629d8fece4cb71b5195336e4473e6a3079a3911a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2013 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #ifndef FOLLY_STLALLOCATOR_H_
 #define FOLLY_STLALLOCATOR_H_
 
+#include "folly/Traits.h"
+
 #include <memory>
+#include <limits>
+#include <utility>
+#include <exception>
+#include <stdexcept>
+
+#include <cstddef>
 
 namespace folly {
 
 /**
- * Wrap a simple allocator into a STL-compliant allocator.
+ * Wrap a SimpleAllocator into a STL-compliant allocator.
  *
- * The simple allocator must provide two methods:
+ * The SimpleAllocator must provide two methods:
  *    void* allocate(size_t size);
  *    void deallocate(void* ptr, size_t size);
  * which, respectively, allocate a block of size bytes (aligned to the maximum
@@ -45,6 +53,8 @@ namespace folly {
  *     free(p);
  *   }
  * };
+ *
+ * author: Tudor Bosman <tudorb@fb.com>
  */
 
 // This would be so much simpler with std::allocator_traits, but gcc 4.6.2
@@ -128,7 +138,126 @@ class StlAllocator {
   Alloc* alloc_;
 };
 
+/*
+ * Helper classes/functions for creating a unique_ptr using a custom allocator
+ *
+ * @author: Marcelo Juchem <marcelo@fb.com>
+ */
+
+// A deleter implementation based on std::default_delete,
+// which uses a custom allocator to free memory
+template <typename Allocator>
+class allocator_delete {
+  typedef typename std::remove_reference<Allocator>::type allocator_type;
+
+public:
+  allocator_delete() = default;
+
+  explicit allocator_delete(const allocator_type& allocator):
+    allocator_(allocator)
+  {}
+
+  explicit allocator_delete(allocator_type&& allocator):
+    allocator_(std::move(allocator))
+  {}
+
+  template <typename U>
+  allocator_delete(const allocator_delete<U>& other):
+    allocator_(other.get_allocator())
+  {}
+
+  allocator_type& get_allocator() const {
+    return allocator_;
+  }
+
+  void operator()(typename allocator_type::pointer p) const {
+    if (!p) {
+      return;
+    }
+
+    allocator_.destroy(p);
+    allocator_.deallocate(p, 1);
+  }
+
+private:
+  mutable allocator_type allocator_;
+};
+
+template <typename T, typename Allocator>
+class is_simple_allocator {
+  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_destroy, destroy);
+
+  typedef typename std::remove_const<
+    typename std::remove_reference<Allocator>::type
+  >::type allocator;
+  typedef T value_type;
+  typedef value_type* pointer;
+
+public:
+  constexpr static bool value = !has_destroy<allocator, void(pointer)>::value
+    && !has_destroy<allocator, void(void*)>::value;
+};
+
+template <typename T, typename Allocator>
+typename std::enable_if<
+  is_simple_allocator<T, Allocator>::value,
+  folly::StlAllocator<typename std::remove_reference<Allocator>::type, T>
+>::type make_stl_allocator(Allocator&& allocator) {
+  return folly::StlAllocator<
+    typename std::remove_reference<Allocator>::type, T
+  >(&allocator);
+}
+
+template <typename T, typename Allocator>
+typename std::enable_if<
+  !is_simple_allocator<T, Allocator>::value,
+  typename std::remove_reference<Allocator>::type
+>::type make_stl_allocator(Allocator&& allocator) {
+  return std::move(allocator);
+}
+
+template <typename T, typename Allocator>
+struct AllocatorUniquePtr {
+  typedef std::unique_ptr<T,
+    folly::allocator_delete<
+      typename std::conditional<
+        is_simple_allocator<T, Allocator>::value,
+        folly::StlAllocator<typename std::remove_reference<Allocator>::type, T>,
+        typename std::remove_reference<Allocator>::type
+      >::type
+    >
+  > type;
+};
+
+template <typename T, typename Allocator, typename ...Args>
+typename AllocatorUniquePtr<T, Allocator>::type allocate_unique(
+  Allocator&& allocator, Args&&... args
+) {
+  auto stlAllocator = folly::make_stl_allocator<T>(
+    std::forward<Allocator>(allocator)
+  );
+  auto p = stlAllocator.allocate(1);
+
+  try {
+    stlAllocator.construct(p, std::forward<Args>(args)...);
+
+    return {p,
+      folly::allocator_delete<decltype(stlAllocator)>(std::move(stlAllocator))
+    };
+  } catch (...) {
+    stlAllocator.deallocate(p, 1);
+    throw;
+  }
+}
+
+template <typename T, typename Allocator, typename ...Args>
+std::shared_ptr<T> allocate_shared(Allocator&& allocator, Args&&... args) {
+  return std::allocate_shared<T>(
+    folly::make_stl_allocator<T>(std::forward<Allocator>(allocator)),
+    std::forward<Args>(args)...
+  );
+}
+
 }  // namespace folly
 
 #endif /* FOLLY_STLALLOCATOR_H_ */
-
index 7432dec5c2ed95645cfcf9f71100ff73dd745aed..d2fad50ee842d3be52c9881bc5bbd32da96db52c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2013 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -292,4 +292,72 @@ FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(std::function);
 // Boost
 FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(boost::shared_ptr);
 
+#define FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, cv_qual) \
+  template <typename TTheClass_, typename RTheReturn_, typename... TTheArgs_> \
+  class classname<TTheClass_, RTheReturn_(TTheArgs_...) cv_qual> { \
+    template < \
+      typename UTheClass_, RTheReturn_ (UTheClass_::*)(TTheArgs_...) cv_qual \
+    > struct sfinae {}; \
+    template <typename UTheClass_> \
+    constexpr static bool test(sfinae<UTheClass_, &UTheClass_::func_name>*) \
+    { return true; } \
+    template <typename> \
+    constexpr static bool test(...) { return false; } \
+  public: \
+    constexpr static bool value = test<TTheClass_>(nullptr); \
+  }
+
+/*
+ * The FOLLY_CREATE_HAS_MEMBER_FN_TRAITS is used to create traits
+ * classes that check for the existence of a member function with
+ * a given name and signature. It currently does not support
+ * checking for inherited members.
+ *
+ * Such classes receive two template parameters: the class to be checked
+ * and the signature of the member function. A static boolean field
+ * named `value` (which is also constexpr) tells whether such member
+ * function exists.
+ *
+ * Each traits class created is bound only to the member name, not to
+ * its signature nor to the type of the class containing it.
+ *
+ * Say you need to know if a given class has a member function named
+ * `test` with the following signature:
+ *
+ *    int test() const;
+ *
+ * You'd need this macro to create a traits class to check for a member
+ * named `test`, and then use this traits class to check for the signature:
+ *
+ * namespace {
+ *
+ * FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
+ *
+ * } // unnamed-namespace
+ *
+ * void some_func() {
+ *   cout << "Does class Foo have a member int test() const? "
+ *     << boolalpha << has_test_traits<Foo, int() const>::value;
+ * }
+ *
+ * You can use the same traits class to test for a completely different
+ * signature, on a completely different class, as long as the member name
+ * is the same:
+ *
+ * void some_func() {
+ *   cout << "Does class Foo have a member int test()? "
+ *     << boolalpha << has_test_traits<Foo, int()>::value;
+ *   cout << "Does class Foo have a member int test() const? "
+ *     << boolalpha << has_test_traits<Foo, int() const>::value;
+ *   cout << "Does class Bar have a member double test(const string&, long)? "
+ *     << boolalpha << has_test_traits<Bar, double(const string&, long)>::value;
+ * }
+ */
+#define FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(classname, func_name) \
+  template <typename, typename> class classname; \
+  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, ); \
+  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, const); \
+  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, volatile); \
+  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, volatile const)
+
 #endif //FOLLY_BASE_TRAITS_H_
diff --git a/folly/test/ArenaSmartPtrTest.cpp b/folly/test/ArenaSmartPtrTest.cpp
new file mode 100644 (file)
index 0000000..5653411
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * @author: Marcelo Juchem <marcelo@fb.com>
+ */
+
+#include "folly/StlAllocator.h"
+#include "folly/Arena.h"
+
+#include <gtest/gtest.h>
+
+using namespace folly;
+
+struct global_counter {
+  global_counter(): count_(0) {}
+
+  void increase() { ++count_; }
+  void decrease() {
+    EXPECT_GT(count_, 0);
+    --count_;
+  }
+
+  unsigned count() const { return count_; }
+
+private:
+  unsigned count_;
+};
+
+struct Foo {
+  explicit Foo(global_counter& counter):
+    counter_(counter)
+  {
+    counter_.increase();
+  }
+
+  ~Foo() {
+    counter_.decrease();
+  }
+
+private:
+  global_counter& counter_;
+};
+
+template <typename Allocator>
+void unique_ptr_test(Allocator& allocator) {
+  typedef typename AllocatorUniquePtr<Foo, Allocator>::type ptr_type;
+
+  global_counter counter;
+  EXPECT_EQ(counter.count(), 0);
+
+  Foo* foo = nullptr;
+
+  {
+    auto p = folly::allocate_unique<Foo>(allocator, counter);
+    EXPECT_EQ(counter.count(), 1);
+
+    p.reset();
+    EXPECT_EQ(counter.count(), 0);
+
+    p = folly::allocate_unique<Foo>(allocator, counter);
+    EXPECT_EQ(counter.count(), 1);
+
+    foo = p.release();
+    EXPECT_EQ(counter.count(), 1);
+  }
+  EXPECT_EQ(counter.count(), 1);
+
+  {
+    auto p = folly::allocate_unique<Foo>(allocator, counter);
+    EXPECT_EQ(counter.count(), 2);
+
+    [&](ptr_type g) {
+      EXPECT_EQ(counter.count(), 2);
+      g.reset();
+      EXPECT_EQ(counter.count(), 1);
+    }(std::move(p));
+  }
+  EXPECT_EQ(counter.count(), 1);
+
+  StlAllocator<Allocator, Foo>().destroy(foo);
+  EXPECT_EQ(counter.count(), 0);
+}
+
+TEST(ArenaSmartPtr, unique_ptr_SysArena) {
+  SysArena arena;
+  unique_ptr_test(arena);
+}
+
+TEST(ArenaSmartPtr, unique_ptr_StlAlloc_SysArena) {
+  SysArena arena;
+  StlAllocator<SysArena, Foo> alloc(&arena);
+  unique_ptr_test(alloc);
+}
+
+template <typename Allocator>
+void shared_ptr_test(Allocator& allocator) {
+  typedef std::shared_ptr<Foo> ptr_type;
+
+  global_counter counter;
+  EXPECT_EQ(counter.count(), 0);
+
+  ptr_type foo;
+  EXPECT_EQ(counter.count(), 0);
+  EXPECT_EQ(foo.use_count(), 0);
+
+  {
+    auto p = folly::allocate_shared<Foo>(allocator, counter);
+    EXPECT_EQ(counter.count(), 1);
+    EXPECT_EQ(p.use_count(), 1);
+
+    p.reset();
+    EXPECT_EQ(counter.count(), 0);
+    EXPECT_EQ(p.use_count(), 0);
+
+    p = folly::allocate_shared<Foo>(allocator, counter);
+    EXPECT_EQ(counter.count(), 1);
+    EXPECT_EQ(p.use_count(), 1);
+
+    foo = p;
+    EXPECT_EQ(p.use_count(), 2);
+  }
+  EXPECT_EQ(counter.count(), 1);
+  EXPECT_EQ(foo.use_count(), 1);
+
+  {
+    auto p = foo;
+    EXPECT_EQ(counter.count(), 1);
+    EXPECT_EQ(p.use_count(), 2);
+
+    [&](ptr_type g) {
+      EXPECT_EQ(counter.count(), 1);
+      EXPECT_EQ(p.use_count(), 3);
+      EXPECT_EQ(g.use_count(), 3);
+      g.reset();
+      EXPECT_EQ(counter.count(), 1);
+      EXPECT_EQ(p.use_count(), 2);
+      EXPECT_EQ(g.use_count(), 0);
+    }(p);
+    EXPECT_EQ(counter.count(), 1);
+    EXPECT_EQ(p.use_count(), 2);
+  }
+  EXPECT_EQ(counter.count(), 1);
+  EXPECT_EQ(foo.use_count(), 1);
+
+  foo.reset();
+  EXPECT_EQ(counter.count(), 0);
+  EXPECT_EQ(foo.use_count(), 0);
+}
+
+TEST(ArenaSmartPtr, shared_ptr_SysArena) {
+  SysArena arena;
+  shared_ptr_test(arena);
+}
+
+TEST(ArenaSmartPtr, shared_ptr_StlAlloc_SysArena) {
+  SysArena arena;
+  StlAllocator<SysArena, Foo> alloc(&arena);
+  shared_ptr_test(alloc);
+}
+
+int main(int argc, char *argv[]) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/folly/test/HasMemberFnTraitsTest.cpp b/folly/test/HasMemberFnTraitsTest.cpp
new file mode 100644 (file)
index 0000000..50efce8
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * @author: Marcelo Juchem <marcelo@fb.com>
+ */
+
+#include "folly/Traits.h"
+
+#include <gtest/gtest.h>
+#include <glog/logging.h>
+
+#include <string>
+
+using namespace std;
+using namespace folly;
+
+FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test, test);
+
+struct Foo {
+  int test();
+  int test() const;
+  string test(const string&) const;
+};
+
+struct Bar {
+  int test();
+  double test(int,long);
+  long test(int) const;
+};
+
+struct Gaz {
+  void test();
+  void test() const;
+  void test() volatile;
+  void test() const volatile;
+};
+
+struct NoCV {
+  void test();
+};
+
+struct Const {
+  void test() const;
+};
+
+struct Volatile {
+  void test() volatile;
+};
+
+struct CV {
+  void test() const volatile;
+};
+
+#define LOG_VALUE(x) []() { \
+  LOG(INFO) << #x << ": " << boolalpha << (x); \
+  return x; \
+}()
+
+TEST(HasMemberFnTraits, DirectMembers) {
+  EXPECT_TRUE(LOG_VALUE((has_test<Foo, int()>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Foo, int() const>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Foo, double(int, long)>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Foo, string(const string&) const>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Foo, long(int) const>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Foo, string(string) const>::value)));
+
+  EXPECT_TRUE(LOG_VALUE((has_test<Bar, int()>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Bar, int() const>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Bar, double(int, long)>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Bar, string(const string&) const>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Bar, long(int) const>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Bar, string(string) const>::value)));
+
+  EXPECT_TRUE(LOG_VALUE((has_test<Gaz, void()>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Gaz, void() const>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Gaz, void() volatile>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Gaz, void() const volatile>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Gaz, void() volatile const>::value)));
+
+  EXPECT_TRUE(LOG_VALUE((has_test<NoCV, void()>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<NoCV, void() const>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<NoCV, void() volatile>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<NoCV, void() const volatile>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<NoCV, void() volatile const>::value)));
+
+  EXPECT_FALSE(LOG_VALUE((has_test<Const, void()>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Const, void() const>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Const, void() volatile>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Const, void() const volatile>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Const, void() volatile const>::value)));
+
+  EXPECT_FALSE(LOG_VALUE((has_test<Volatile, void()>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Volatile, void() const>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<Volatile, void() volatile>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Volatile, void() const volatile>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<Volatile, void() volatile const>::value)));
+
+  EXPECT_FALSE(LOG_VALUE((has_test<CV, void()>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<CV, void() const>::value)));
+  EXPECT_FALSE(LOG_VALUE((has_test<CV, void() volatile>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<CV, void() const volatile>::value)));
+  EXPECT_TRUE(LOG_VALUE((has_test<CV, void() volatile const>::value)));
+}
+
+int main(int argc, char *argv[]) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}