Skip to content

Commit

Permalink
Add a type-erased shared pointer (open-telemetry#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
Johannes Tax authored Mar 10, 2020
1 parent 97ea4d7 commit 7b4dd48
Show file tree
Hide file tree
Showing 4 changed files with 374 additions and 1 deletion.
182 changes: 182 additions & 0 deletions api/include/opentelemetry/nostd/shared_ptr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#pragma once

#include <memory>

#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace nostd
{
/**
* Provide a type-erased version of std::shared_ptr that has ABI stability.
*/
template <class T>
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<T> &&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 <class U,
typename std::enable_if<std::is_convertible<pointer, U *>::value>::type * = nullptr>
void MoveTo(typename shared_ptr<U>::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<T> 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<T> ptr_(ptr);
new (buffer_.data) shared_ptr_wrapper{std::move(ptr_)};
}

shared_ptr(std::shared_ptr<T> ptr) noexcept
{
new (buffer_.data) shared_ptr_wrapper{std::move(ptr)};
}

shared_ptr(shared_ptr &&other) noexcept { other.wrapper().MoveTo(buffer_); }

template <class U,
typename std::enable_if<std::is_convertible<U *, pointer>::value>::type * = nullptr>
shared_ptr(shared_ptr<U> &&other) noexcept
{
other.wrapper().template MoveTo<T>(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<T> &other) noexcept
{
shared_ptr<T> tmp{std::move(other)};

wrapper().MoveTo(other.buffer_);
tmp.wrapper().MoveTo(buffer_);
}

template <typename U>
friend class shared_ptr;

private:
PlacementBuffer buffer_;

shared_ptr_wrapper &wrapper() noexcept
{
return *reinterpret_cast<shared_ptr_wrapper *>(buffer_.data);
}

const shared_ptr_wrapper &wrapper() const noexcept
{
return *reinterpret_cast<const shared_ptr_wrapper *>(buffer_.data);
}
};

template <class T1, class T2>
bool operator!=(const shared_ptr<T1> &lhs, const shared_ptr<T2> &rhs) noexcept
{
return lhs.get() != rhs.get();
}

template <class T1, class T2>
bool operator==(const shared_ptr<T1> &lhs, const shared_ptr<T2> &rhs) noexcept
{
return lhs.get() == rhs.get();
}

template <class T>
inline bool operator==(const shared_ptr<T> &lhs, std::nullptr_t) noexcept
{
return lhs.get() == nullptr;
}

template <class T>
inline bool operator==(std::nullptr_t, const shared_ptr<T> &rhs) noexcept
{
return nullptr == rhs.get();
}

template <class T>
inline bool operator!=(const shared_ptr<T> &lhs, std::nullptr_t) noexcept
{
return lhs.get() != nullptr;
}

template <class T>
inline bool operator!=(std::nullptr_t, const shared_ptr<T> &rhs) noexcept
{
return nullptr != rhs.get();
}
} // namespace nostd
OPENTELEMETRY_END_NAMESPACE
11 changes: 11 additions & 0 deletions api/test/nostd/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
3 changes: 2 additions & 1 deletion api/test/nostd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
179 changes: 179 additions & 0 deletions api/test/nostd/shared_ptr_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#include "opentelemetry/nostd/shared_ptr.h"

#include <gtest/gtest.h>

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<int> ptr1;
EXPECT_EQ(ptr1.get(), nullptr);

shared_ptr<int> ptr2{nullptr};
EXPECT_EQ(ptr2.get(), nullptr);
}

TEST(SharedPtrTest, ExplicitConstruction)
{
auto c = new C();
shared_ptr<C> ptr1{c};
EXPECT_EQ(ptr1.get(), c);

auto d = new D();
shared_ptr<C> ptr2{d};
EXPECT_EQ(ptr2.get(), d);
}

TEST(SharedPtrTest, MoveConstruction)
{
auto value = new int{123};
shared_ptr<int> ptr1{value};
shared_ptr<int> ptr2{std::move(ptr1)};
EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(ptr2.get(), value);
}

TEST(SharedPtrTest, MoveConstructionFromDifferentType)
{
auto value = new int{123};
shared_ptr<int> ptr1{value};
shared_ptr<const int> 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<int> ptr1{value};
shared_ptr<int> ptr2{std::move(ptr1)};
EXPECT_EQ(ptr1.get(), nullptr);
EXPECT_EQ(ptr2.get(), value);
}

TEST(SharedPtrTest, Destruction)
{
bool was_destructed;
shared_ptr<A>{new A{was_destructed}};
EXPECT_TRUE(was_destructed);
}

TEST(SharedPtrTest, Assignment)
{
auto value = new int{123};
shared_ptr<int> ptr1;

ptr1 = shared_ptr<int>(value);
EXPECT_EQ(ptr1.get(), value);

ptr1 = nullptr;
EXPECT_EQ(ptr1.get(), nullptr);

auto value2 = new int{234};
const shared_ptr<int> ptr2 = shared_ptr<int>(value2);
ptr1 = ptr2;
EXPECT_EQ(ptr1.get(), value2);

auto value3 = new int{345};
std::shared_ptr<int> ptr3(value3);
ptr1 = ptr3;
EXPECT_EQ(ptr1.get(), value3);
}

TEST(SharedPtrTest, BoolConversionOpertor)
{
auto value = new int{123};
shared_ptr<int> ptr1{value};

EXPECT_TRUE(ptr1);
EXPECT_FALSE(shared_ptr<int>{});
}

TEST(SharedPtrTest, PointerOperators)
{
auto value = new int{123};
shared_ptr<int> ptr1{value};

EXPECT_EQ(&*ptr1, value);
EXPECT_EQ(
shared_ptr<B> {}->f(), 123);
}

TEST(SharedPtrTest, Swap)
{
auto value1 = new int{123};
shared_ptr<int> ptr1{value1};

auto value2 = new int{456};
shared_ptr<int> ptr2{value2};
ptr1.swap(ptr2);

EXPECT_EQ(ptr1.get(), value2);
EXPECT_EQ(ptr2.get(), value1);
}

TEST(SharedPtrTest, Comparison)
{
shared_ptr<int> ptr1{new int{123}};
shared_ptr<int> ptr2{new int{456}};
shared_ptr<int> 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<shared_ptr<const int>> nums;

for (int i = 10; i > 0; i--)
{
nums.push_back(shared_ptr<int>(new int(i)));
}

auto nums2 = nums;

std::sort(nums.begin(), nums.end(),
[](shared_ptr<const int> a, shared_ptr<const int> b) { return *a < *b; });

EXPECT_NE(nums, nums2);

std::reverse(nums2.begin(), nums2.end());

EXPECT_EQ(nums, nums2);
}

0 comments on commit 7b4dd48

Please sign in to comment.