From 7b4dd48b87aa64ffa3f420b75d5799acc621b1b5 Mon Sep 17 00:00:00 2001 From: Johannes Tax Date: Tue, 10 Mar 2020 09:32:27 -0700 Subject: [PATCH] Add a type-erased shared pointer (#47) --- api/include/opentelemetry/nostd/shared_ptr.h | 182 +++++++++++++++++++ api/test/nostd/BUILD | 11 ++ api/test/nostd/CMakeLists.txt | 3 +- api/test/nostd/shared_ptr_test.cc | 179 ++++++++++++++++++ 4 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 api/include/opentelemetry/nostd/shared_ptr.h create mode 100644 api/test/nostd/shared_ptr_test.cc diff --git a/api/include/opentelemetry/nostd/shared_ptr.h b/api/include/opentelemetry/nostd/shared_ptr.h new file mode 100644 index 00000000000..7d84b6f4084 --- /dev/null +++ b/api/include/opentelemetry/nostd/shared_ptr.h @@ -0,0 +1,182 @@ +#pragma once + +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace nostd +{ +/** + * Provide a type-erased version of std::shared_ptr that has ABI stability. + */ +template +class shared_ptr +{ +public: + using element_type = T; + using pointer = element_type *; + +private: + static constexpr size_t kMaxSize = 32; + static constexpr size_t kAlignment = 8; + + struct alignas(kAlignment) PlacementBuffer + { + char data[kMaxSize]; + }; + + class shared_ptr_wrapper + { + public: + shared_ptr_wrapper() noexcept = default; + + shared_ptr_wrapper(std::shared_ptr &&ptr) noexcept : ptr_{std::move(ptr)} {} + + virtual ~shared_ptr_wrapper() {} + + virtual void CopyTo(PlacementBuffer &buffer) const noexcept + { + new (buffer.data) shared_ptr_wrapper{*this}; + } + + virtual void MoveTo(PlacementBuffer &buffer) noexcept + { + new (buffer.data) shared_ptr_wrapper{std::move(this->ptr_)}; + } + + template ::value>::type * = nullptr> + void MoveTo(typename shared_ptr::PlacementBuffer &buffer) noexcept + { + new (buffer.data) shared_ptr_wrapper{std::move(this->ptr_)}; + } + + virtual pointer Get() const noexcept { return ptr_.get(); } + + virtual void Reset() noexcept { ptr_.reset(); } + + private: + std::shared_ptr ptr_; + }; + + static_assert(sizeof(shared_ptr_wrapper) <= kMaxSize, "Placement buffer is too small"); + static_assert(alignof(shared_ptr_wrapper) <= kAlignment, "Placement buffer not properly aligned"); + +public: + shared_ptr() noexcept { new (buffer_.data) shared_ptr_wrapper{}; } + + explicit shared_ptr(pointer ptr) + { + std::shared_ptr ptr_(ptr); + new (buffer_.data) shared_ptr_wrapper{std::move(ptr_)}; + } + + shared_ptr(std::shared_ptr ptr) noexcept + { + new (buffer_.data) shared_ptr_wrapper{std::move(ptr)}; + } + + shared_ptr(shared_ptr &&other) noexcept { other.wrapper().MoveTo(buffer_); } + + template ::value>::type * = nullptr> + shared_ptr(shared_ptr &&other) noexcept + { + other.wrapper().template MoveTo(buffer_); + } + + shared_ptr(const shared_ptr &other) noexcept { other.wrapper().CopyTo(buffer_); } + + ~shared_ptr() { wrapper().~shared_ptr_wrapper(); } + + shared_ptr &operator=(shared_ptr &&other) noexcept + { + wrapper().~shared_ptr_wrapper(); + other.wrapper().MoveTo(buffer_); + return *this; + } + + shared_ptr &operator=(std::nullptr_t) noexcept + { + wrapper().Reset(); + return *this; + } + + shared_ptr &operator=(const shared_ptr &other) noexcept + { + wrapper().~shared_ptr_wrapper(); + other.wrapper().CopyTo(buffer_); + return *this; + } + + element_type &operator*() const noexcept { return *wrapper().Get(); } + + pointer operator->() const noexcept { return wrapper().Get(); } + + operator bool() const noexcept { return wrapper().Get() != nullptr; } + + pointer get() const noexcept { return wrapper().Get(); } + + void swap(shared_ptr &other) noexcept + { + shared_ptr tmp{std::move(other)}; + + wrapper().MoveTo(other.buffer_); + tmp.wrapper().MoveTo(buffer_); + } + + template + friend class shared_ptr; + +private: + PlacementBuffer buffer_; + + shared_ptr_wrapper &wrapper() noexcept + { + return *reinterpret_cast(buffer_.data); + } + + const shared_ptr_wrapper &wrapper() const noexcept + { + return *reinterpret_cast(buffer_.data); + } +}; + +template +bool operator!=(const shared_ptr &lhs, const shared_ptr &rhs) noexcept +{ + return lhs.get() != rhs.get(); +} + +template +bool operator==(const shared_ptr &lhs, const shared_ptr &rhs) noexcept +{ + return lhs.get() == rhs.get(); +} + +template +inline bool operator==(const shared_ptr &lhs, std::nullptr_t) noexcept +{ + return lhs.get() == nullptr; +} + +template +inline bool operator==(std::nullptr_t, const shared_ptr &rhs) noexcept +{ + return nullptr == rhs.get(); +} + +template +inline bool operator!=(const shared_ptr &lhs, std::nullptr_t) noexcept +{ + return lhs.get() != nullptr; +} + +template +inline bool operator!=(std::nullptr_t, const shared_ptr &rhs) noexcept +{ + return nullptr != rhs.get(); +} +} // namespace nostd +OPENTELEMETRY_END_NAMESPACE diff --git a/api/test/nostd/BUILD b/api/test/nostd/BUILD index bc7513fe270..c3f2d6864f9 100644 --- a/api/test/nostd/BUILD +++ b/api/test/nostd/BUILD @@ -41,3 +41,14 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "shared_ptr_test", + srcs = [ + "shared_ptr_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/api/test/nostd/CMakeLists.txt b/api/test/nostd/CMakeLists.txt index 3f561a5e669..60d833f0cda 100644 --- a/api/test/nostd/CMakeLists.txt +++ b/api/test/nostd/CMakeLists.txt @@ -1,6 +1,7 @@ include(GoogleTest) -foreach(testname string_view_test unique_ptr_test utility_test span_test) +foreach(testname string_view_test unique_ptr_test utility_test span_test + shared_ptr_test) add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) diff --git a/api/test/nostd/shared_ptr_test.cc b/api/test/nostd/shared_ptr_test.cc new file mode 100644 index 00000000000..7f18d3bdb50 --- /dev/null +++ b/api/test/nostd/shared_ptr_test.cc @@ -0,0 +1,179 @@ +#include "opentelemetry/nostd/shared_ptr.h" + +#include + +using opentelemetry::nostd::shared_ptr; + +class A +{ +public: + explicit A(bool &destructed) noexcept : destructed_{destructed} { destructed_ = false; } + + ~A() { destructed_ = true; } + +private: + bool &destructed_; +}; + +class B +{ +public: + int f() const { return 123; } +}; + +class C +{ +public: + virtual ~C() {} +}; + +class D : public C +{ +public: + virtual ~D() {} +}; + +TEST(SharedPtrTest, DefaultConstruction) +{ + shared_ptr ptr1; + EXPECT_EQ(ptr1.get(), nullptr); + + shared_ptr ptr2{nullptr}; + EXPECT_EQ(ptr2.get(), nullptr); +} + +TEST(SharedPtrTest, ExplicitConstruction) +{ + auto c = new C(); + shared_ptr ptr1{c}; + EXPECT_EQ(ptr1.get(), c); + + auto d = new D(); + shared_ptr ptr2{d}; + EXPECT_EQ(ptr2.get(), d); +} + +TEST(SharedPtrTest, MoveConstruction) +{ + auto value = new int{123}; + shared_ptr ptr1{value}; + shared_ptr ptr2{std::move(ptr1)}; + EXPECT_EQ(ptr1.get(), nullptr); + EXPECT_EQ(ptr2.get(), value); +} + +TEST(SharedPtrTest, MoveConstructionFromDifferentType) +{ + auto value = new int{123}; + shared_ptr ptr1{value}; + shared_ptr ptr2{std::move(ptr1)}; + EXPECT_EQ(ptr1.get(), nullptr); + EXPECT_EQ(ptr2.get(), value); +} + +TEST(SharedPtrTest, MoveConstructionFromStdSharedPtr) +{ + auto value = new int{123}; + std::shared_ptr ptr1{value}; + shared_ptr ptr2{std::move(ptr1)}; + EXPECT_EQ(ptr1.get(), nullptr); + EXPECT_EQ(ptr2.get(), value); +} + +TEST(SharedPtrTest, Destruction) +{ + bool was_destructed; + shared_ptr{new A{was_destructed}}; + EXPECT_TRUE(was_destructed); +} + +TEST(SharedPtrTest, Assignment) +{ + auto value = new int{123}; + shared_ptr ptr1; + + ptr1 = shared_ptr(value); + EXPECT_EQ(ptr1.get(), value); + + ptr1 = nullptr; + EXPECT_EQ(ptr1.get(), nullptr); + + auto value2 = new int{234}; + const shared_ptr ptr2 = shared_ptr(value2); + ptr1 = ptr2; + EXPECT_EQ(ptr1.get(), value2); + + auto value3 = new int{345}; + std::shared_ptr ptr3(value3); + ptr1 = ptr3; + EXPECT_EQ(ptr1.get(), value3); +} + +TEST(SharedPtrTest, BoolConversionOpertor) +{ + auto value = new int{123}; + shared_ptr ptr1{value}; + + EXPECT_TRUE(ptr1); + EXPECT_FALSE(shared_ptr{}); +} + +TEST(SharedPtrTest, PointerOperators) +{ + auto value = new int{123}; + shared_ptr ptr1{value}; + + EXPECT_EQ(&*ptr1, value); + EXPECT_EQ( + shared_ptr {}->f(), 123); +} + +TEST(SharedPtrTest, Swap) +{ + auto value1 = new int{123}; + shared_ptr ptr1{value1}; + + auto value2 = new int{456}; + shared_ptr ptr2{value2}; + ptr1.swap(ptr2); + + EXPECT_EQ(ptr1.get(), value2); + EXPECT_EQ(ptr2.get(), value1); +} + +TEST(SharedPtrTest, Comparison) +{ + shared_ptr ptr1{new int{123}}; + shared_ptr ptr2{new int{456}}; + shared_ptr ptr3{}; + + EXPECT_EQ(ptr1, ptr1); + EXPECT_NE(ptr1, ptr2); + + EXPECT_NE(ptr1, nullptr); + EXPECT_NE(nullptr, ptr1); + + EXPECT_EQ(ptr3, nullptr); + EXPECT_EQ(nullptr, ptr3); +} + +TEST(SharedPtrTest, Sort) +{ + std::vector> nums; + + for (int i = 10; i > 0; i--) + { + nums.push_back(shared_ptr(new int(i))); + } + + auto nums2 = nums; + + std::sort(nums.begin(), nums.end(), + [](shared_ptr a, shared_ptr b) { return *a < *b; }); + + EXPECT_NE(nums, nums2); + + std::reverse(nums2.begin(), nums2.end()); + + EXPECT_EQ(nums, nums2); +}