Skip to content

Commit

Permalink
shared_from_this_ptr
Browse files Browse the repository at this point in the history
Summary: A smart-pointer type for the shared-from-this case.

Reviewed By: Gownta, DenisYaroshevskiy, skrueger

Differential Revision: D68971085

fbshipit-source-id: 3a07218aba7403e293013f694ed09ac58f1244a1
  • Loading branch information
yfeldblum authored and facebook-github-bot committed Feb 6, 2025
1 parent d653344 commit c797815
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,8 @@ if (BUILD_TESTS OR BUILD_BENCHMARKS)
TEST memory_arena_test WINDOWS_DISABLED SOURCES ArenaTest.cpp
TEST memory_reentrant_allocator_test WINDOWS_DISABLED
SOURCES ReentrantAllocatorTest.cpp
TEST memory_shared_from_this_ptr_test
SOURCES shared_from_this_ptr_test.cpp
TEST memory_thread_cached_arena_test WINDOWS_DISABLED
SOURCES ThreadCachedArenaTest.cpp
TEST memory_mallctl_helper_test SOURCES MallctlHelperTest.cpp
Expand Down
8 changes: 8 additions & 0 deletions folly/memory/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ cpp_library(
],
)

cpp_library(
name = "shared_from_this_ptr",
headers = ["shared_from_this_ptr.h"],
exported_deps = [
"//folly:traits",
],
)

cpp_library(
name = "thread_cached_arena",
srcs = ["ThreadCachedArena.cpp"],
Expand Down
136 changes: 136 additions & 0 deletions folly/memory/shared_from_this_ptr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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.
*/

#include <cstring>
#include <memory>
#include <new>
#include <utility>

#include <folly/Traits.h>

namespace folly {

/// shared_from_this_ptr
///
/// Like std::shared_ptr, but one pointer wide rather than two pointers wide.
/// Suitable for types publicly inheritng std::enable_shared_from_this. However,
/// only requires the type publicly to expose member shared_from_this.
///
/// Useful for cases which may hold large numbers of shared-ptr copies to the
/// same managed object, which implements shared-from-this.
///
/// Necessary as v.s. boost::intrusive_ptr for cases which use weak-ptr to the
/// managed objects as well as shared-ptr, and for cases which already use
/// shared-ptr in the interface in a way which is difficult to change.
///
/// TODO: Optimize refcount ops. Likely requires manipulating std::shared_ptr
/// internals in non-portable, library-specific ways.
template <typename T>
class shared_from_this_ptr {
private:
using holder = shared_from_this_ptr;
using shared = std::shared_ptr<T>;

T* ptr_{};

static void incr(T& ref) noexcept {
auto ptr = ref.shared_from_this();
folly::aligned_storage_for_t<std::shared_ptr<T>> storage;
::new (&storage) shared(std::move(ptr));
}
static void decr(T& ref) noexcept {
auto ptr = ref.shared_from_this();
folly::aligned_storage_for_t<std::shared_ptr<T>> storage;
std::memcpy(&storage, &ptr, sizeof(ptr));
reinterpret_cast<shared&>(storage).~shared();
}
static void incr(T* ptr) noexcept { !ptr ? void() : incr(*ptr); }
static void decr(T* ptr) noexcept { !ptr ? void() : decr(*ptr); }

void assign(T* ptr) noexcept {
incr(ptr);
decr(ptr_);
ptr_ = ptr;
}

public:
using element_type = typename shared::element_type;

shared_from_this_ptr() = default;
explicit shared_from_this_ptr(shared&& ptr) noexcept
: ptr_{std::exchange(ptr, {}).get()} {
incr(ptr_);
}
explicit shared_from_this_ptr(shared const& ptr) noexcept : ptr_{ptr.get()} {
incr(ptr_);
}
shared_from_this_ptr(holder&& that) noexcept
: ptr_{std::exchange(that.ptr_, {})} {}
shared_from_this_ptr(holder const& that) noexcept : ptr_{that.ptr_} {
incr(ptr_);
}
~shared_from_this_ptr() { decr(ptr_); }

holder& operator=(holder&& that) noexcept {
if (this != &that) {
decr(ptr_);
ptr_ = std::exchange(that.ptr_, {});
}
return *this;
}
holder& operator=(holder const& that) noexcept {
assign(that.ptr_);
return *this;
}
holder& operator=(shared&& ptr) noexcept {
assign(std::exchange(ptr, {}).get());
return *this;
}
holder& operator=(shared const& ptr) noexcept {
assign(ptr.get());
return *this;
}

explicit operator bool() const noexcept { return !!ptr_; }
T* get() const noexcept { return ptr_; }
T* operator->() const noexcept { return ptr_; }
T& operator*() const noexcept { return *ptr_; }

explicit operator shared() const noexcept {
return !ptr_ ? nullptr : ptr_->shared_from_this();
}

void reset() noexcept { assign(nullptr); }
void reset(shared const& ptr) noexcept { assign(ptr.get()); }
void swap(holder& that) noexcept { std::swap(ptr_, that.ptr_); }

friend bool operator==(holder const& holder, std::nullptr_t) noexcept {
return holder.ptr_ == nullptr;
}
friend bool operator!=(holder const& holder, std::nullptr_t) noexcept {
return holder.ptr_ != nullptr;
}
friend bool operator==(std::nullptr_t, holder const& holder) noexcept {
return nullptr == holder.ptr_;
}
friend bool operator!=(std::nullptr_t, holder const& holder) noexcept {
return nullptr != holder.ptr_;
}

friend void swap(holder& a, holder& b) noexcept { a.swap(b); }
};

} // namespace folly
9 changes: 9 additions & 0 deletions folly/memory/test/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ cpp_unittest(
],
)

cpp_unittest(
name = "shared_from_this_ptr_test",
srcs = ["shared_from_this_ptr_test.cpp"],
deps = [
"//folly/memory:shared_from_this_ptr",
"//folly/portability:gtest",
],
)

cpp_unittest(
name = "thread_cached_arena_test",
srcs = ["ThreadCachedArenaTest.cpp"],
Expand Down
187 changes: 187 additions & 0 deletions folly/memory/test/shared_from_this_ptr_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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.
*/

#include <folly/memory/shared_from_this_ptr.h>

#include <memory>
#include <utility>

#include <folly/portability/GTest.h>

struct SharedFromThisPtrTest : testing::Test {};

struct jabberwocky : std::enable_shared_from_this<jabberwocky> {
int data = 3;
};

TEST_F(SharedFromThisPtrTest, empty) {
folly::shared_from_this_ptr<jabberwocky> ptr;
EXPECT_FALSE(bool(ptr));
EXPECT_EQ(nullptr, ptr.get());
}

TEST_F(SharedFromThisPtrTest, copy_shared_constructed) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> ptr{shared};
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, copy_shared_assigned) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> ptr;
ptr = shared;
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, move_shared_constructed) {
auto shared = std::make_shared<jabberwocky>();
auto copy = shared;
folly::shared_from_this_ptr<jabberwocky> ptr{std::move(copy)};
EXPECT_FALSE(bool(copy));
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, move_shared_assigned) {
auto shared = std::make_shared<jabberwocky>();
auto copy = shared;
folly::shared_from_this_ptr<jabberwocky> ptr;
ptr = std::move(copy);
EXPECT_FALSE(bool(copy));
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, copy_constructed) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> orig{shared};
auto ptr = orig;
EXPECT_TRUE(bool(orig));
EXPECT_EQ(shared.get(), orig.get());
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, copy_assigned) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> orig{shared};
folly::shared_from_this_ptr<jabberwocky> ptr;
ptr = orig;
EXPECT_TRUE(bool(orig));
EXPECT_EQ(shared.get(), orig.get());
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, move_constructed) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> orig{shared};
auto ptr = static_cast<decltype(orig)&&>(orig);
EXPECT_FALSE(bool(orig));
EXPECT_EQ(nullptr, orig.get());
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, move_assigned) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> orig{shared};
folly::shared_from_this_ptr<jabberwocky> ptr;
ptr = static_cast<decltype(orig)&&>(orig);
EXPECT_FALSE(bool(orig));
EXPECT_EQ(nullptr, orig.get());
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, copy_assign_self_empty) {
folly::shared_from_this_ptr<jabberwocky> ptr;
ptr = std::as_const(ptr);
EXPECT_FALSE(bool(ptr));
EXPECT_EQ(nullptr, ptr.get());
}

TEST_F(SharedFromThisPtrTest, copy_assign_self) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> ptr{shared};
ptr = std::as_const(ptr);
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, move_assign_self_empty) {
folly::shared_from_this_ptr<jabberwocky> ptr;
ptr = static_cast<decltype(ptr)&&>(ptr);
EXPECT_FALSE(bool(ptr));
EXPECT_EQ(nullptr, ptr.get());
}

TEST_F(SharedFromThisPtrTest, move_assign_self) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> ptr{shared};
ptr = static_cast<decltype(ptr)&&>(ptr);
EXPECT_TRUE(bool(ptr));
EXPECT_EQ(shared.get(), ptr.get());
EXPECT_EQ(&*shared, &*ptr);
EXPECT_EQ(3, ptr->data);
EXPECT_EQ(shared, std::shared_ptr<jabberwocky>(ptr));
}

TEST_F(SharedFromThisPtrTest, empty_equality) {
folly::shared_from_this_ptr<jabberwocky> ptr;
EXPECT_TRUE(nullptr == ptr);
EXPECT_TRUE(ptr == nullptr);
EXPECT_FALSE(nullptr != ptr);
EXPECT_FALSE(ptr != nullptr);
}

TEST_F(SharedFromThisPtrTest, equality) {
auto shared = std::make_shared<jabberwocky>();
folly::shared_from_this_ptr<jabberwocky> ptr{shared};
EXPECT_FALSE(nullptr == ptr);
EXPECT_FALSE(ptr == nullptr);
EXPECT_TRUE(nullptr != ptr);
EXPECT_TRUE(ptr != nullptr);
}

0 comments on commit c797815

Please sign in to comment.