From 772ca510a1b69807551ed46b4bf9d11ab9759471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=B6ttger?= Date: Mon, 30 Oct 2023 12:28:10 -0700 Subject: [PATCH] MaybeManagedPtr Summary: MaybeManagedPtr stores either a raw pointer or a shared_ptr. It provides normal pointer operations on the underlying raw pointer/shared_ptr. When storing a raw pointer, MaybeManagedPtr does not manage the pointer's lifetime, i.e. never calls `free` on the pointer. When storing a shared_ptr, MaybeManagedPtr will release the shared_ptr upon its own destruction. Reviewed By: Gownta Differential Revision: D50015761 fbshipit-source-id: c8adc11ce5b86fe69cbcb5cdfca6a7bdd3404f53 --- folly/MaybeManagedPtr.h | 120 ++++++++++++++ folly/test/MaybeManagedPtrTest.cpp | 252 +++++++++++++++++++++++++++++ 2 files changed, 372 insertions(+) create mode 100644 folly/MaybeManagedPtr.h create mode 100644 folly/test/MaybeManagedPtrTest.cpp diff --git a/folly/MaybeManagedPtr.h b/folly/MaybeManagedPtr.h new file mode 100644 index 00000000000..035521a9d87 --- /dev/null +++ b/folly/MaybeManagedPtr.h @@ -0,0 +1,120 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace folly { + +/** + * MaybeManagedPtr stores either a raw pointer or a shared_ptr. It provides + * normal pointer operations on the underlying raw pointer/shared_ptr. + * + * When storing a raw pointer, MaybeManagedPtr does not manage the pointer's + * lifetime, i.e. never calls `free` on the pointer. + * + * When storing a shared_ptr, MaybeManagedPtr will release the shared_ptr + * upon its own destruction. + */ +template +class MaybeManagedPtr { + public: + /* implicit */ MaybeManagedPtr(T* t) + : t_(std::shared_ptr(std::shared_ptr(), t)) {} + /* implicit */ MaybeManagedPtr(std::shared_ptr t) : t_(t) {} + + /** + * Get pointer to the element contained in MaybeManagedPtr. + * + * @return Pointer to the element contained in MaybeManagedPtr. + */ + [[nodiscard]] T* get() const { return t_.get(); } + + /** + * Return use count of the underlying shared pointer. + * + * @return Use count of the underlying shared pointer. + */ + [[nodiscard]] long useCount() const { return t_.use_count(); } + + /** + * Member of pointer operator + * + * @return Pointer to the element contained in MaybeManagedPtr. + */ + constexpr T* operator->() const { return t_.get(); } + + /** + * Indirection operator + * + * @return Reference to the element contained in MaybeManagedPtr. + */ + constexpr T& operator*() const& { return *t_.get(); } + + /** + * Boolean type conversion operator + * + * @return Returns true if the underlying shared pointer is not + * null. + */ + operator bool() const { return (t_.get() != nullptr); } + + /** + * Boolean equal to operator + * + * @return Returns true if the underlying shared pointer is equal + * to rhs. + */ + bool operator==(T* rhs) const { return t_.get() == rhs; } + + /** + * Boolean equal to operator + * + * @return Returns true if the underlying shared pointer is equal + * to rhs. + */ + bool operator==(const std::shared_ptr& rhs) const { return t_ == rhs; } + + /** + * Boolean not equal to operator + * + * @return Returns true if the underlying shared pointer is not + * equal to rhs. + */ + bool operator!=(T* rhs) const { return !(t_.get() == rhs); } + + /** + * Boolean not equal to operator + * + * @return Returns true if the underlying shared pointer is not + * equal to rhs. + */ + bool operator!=(const std::shared_ptr& rhs) const { return !(t_ == rhs); } + + /** + * Pointer type conversion operator + * + * @return Returns a pointer to the element contained in + * MaybeManagedPtr. + */ + operator T*() const { return t_.get(); } + + private: + std::shared_ptr t_; +}; + +} // namespace folly diff --git a/folly/test/MaybeManagedPtrTest.cpp b/folly/test/MaybeManagedPtrTest.cpp new file mode 100644 index 00000000000..06ab0d87f5d --- /dev/null +++ b/folly/test/MaybeManagedPtrTest.cpp @@ -0,0 +1,252 @@ +/* + * 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 +#include +#include + +using namespace ::testing; + +const int kTestIntValue = 55; + +/** + * This will be used to set up a typed test suite for both possible constructor + * inputs of raw and shared pointer. + */ +template +class MaybeManagedPtrInterfaceTest : public testing::Test { + public: + ~MaybeManagedPtrInterfaceTest() { + // Clean up allocated int object in case of raw pointer. Shared pointer will + // do the deallocation for us when it goes out of scope. + if (std::is_same::value) { + delete intPtr_; + } + } + + protected: + // int object that will be pointed to via raw or shared ptr in tests + int* intPtr_{new int(kTestIntValue)}; + // create raw or shared_ptr to be used with folly::MaybeManagedPtr in tests + T somePtr_ = T(intPtr_); +}; + +using MaybeManagedPtrInterfaceTestTypes = Types>; +TYPED_TEST_SUITE( + MaybeManagedPtrInterfaceTest, MaybeManagedPtrInterfaceTestTypes); + +/** + * Test that we can construct a MaybeManagedPtr from a raw pointer as well as + * from a shared_ptr + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, Constructor) { + folly::MaybeManagedPtr(this->somePtr_); +} + +/** + * Test that the pointer contained in shared pointer points to same address as + * the initial value. + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, GetMethod) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + + EXPECT_EQ(maybeManagedPtr.get(), this->intPtr_); + EXPECT_FALSE( + std::is_const_v>); +} + +/** + * Test that member of pointer operator -> returns a pointer to the same + * address. + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, MemberOfPointerOperator) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + EXPECT_EQ(maybeManagedPtr.operator->(), this->intPtr_); +} + +/** + * Test that indirection operator * returns the value the contained pointer it + * pointing to, as well as making sure it is pointing to the same address. + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, IndirectionOperator) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + + EXPECT_EQ(&(*maybeManagedPtr), this->intPtr_); +} + +/** + * Test equal to operator with raw pointers. + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, EqualToOperatorRawPointer) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + int* otherIntPtr{new int(kTestIntValue + 1)}; + + EXPECT_EQ(maybeManagedPtr, this->intPtr_); + EXPECT_NE(maybeManagedPtr, otherIntPtr); + + delete otherIntPtr; +} + +/** + * Test equal to operator with shared pointers + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, EqualToOperatorSharedPointer) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + + // prevent second shared pointer from deallocating value + auto sameSharedPtr = + std::shared_ptr(std::shared_ptr(), this->intPtr_); + + auto otherSharedPtr = std::shared_ptr(new int(kTestIntValue)); + + EXPECT_EQ(maybeManagedPtr, sameSharedPtr); + EXPECT_NE(maybeManagedPtr, otherSharedPtr); +} + +/** + * Test equal to operator with MaybeManagedPtr + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, EqualToOperatorMaybeManagedPointer) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + + auto sameMaybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + auto otherMaybeManagedPtr = + folly::MaybeManagedPtr(std::shared_ptr(new int(kTestIntValue))); + + EXPECT_EQ(maybeManagedPtr, sameMaybeManagedPtr); + EXPECT_NE(maybeManagedPtr, otherMaybeManagedPtr); +} + +/** + * Test bool type conversion operator + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, BoolTypeConversionOperator) { + auto maybeManagedPtr = folly::MaybeManagedPtr(nullptr); + EXPECT_FALSE(maybeManagedPtr); + + maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + EXPECT_TRUE(maybeManagedPtr); +} + +/** + * Test implicit type conversion operator + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, ImplicitTypeConversionOperator) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + + int* rawPtr = maybeManagedPtr; + EXPECT_EQ(rawPtr, this->intPtr_); +} + +/** + * Test explicit type conversion operator + */ +TYPED_TEST(MaybeManagedPtrInterfaceTest, ExplicitTypeConversionOperator) { + auto maybeManagedPtr = folly::MaybeManagedPtr(this->somePtr_); + + auto rawPtr = (int*)maybeManagedPtr; + EXPECT_EQ(rawPtr, this->intPtr_); +} + +class ContainedObjectMock { + public: + // mock method to ensure object is still alive + MOCK_METHOD(void, checkpoint, ()); + // mock method to be called in destructor to verify object destruction + MOCK_METHOD(void, dtor, ()); + + ~ContainedObjectMock() { dtor(); } +}; + +/** + * Test behavior of MaybeManagedPtr when using raw pointer at construction. We + * expect the contained object *not* to be deallocated when the MaybeManagedPtr + * goes out of scope. + */ +TEST(MaybeManagedPtrBehaviourTest, RawPointer) { + ContainedObjectMock* containedObject{new ContainedObjectMock()}; + + /** + * We call checkpoint once in enclosed scope and outside of scope to ensure + * contained object is still alive after scope exit. We expect destructor to + * be called once to verify object destruction. + */ + testing::InSequence s; + EXPECT_CALL(*containedObject, checkpoint).Times(2); + EXPECT_CALL(*containedObject, dtor).Times(1); + + { + auto rawPtr = containedObject; + auto maybeManagedPtr = folly::MaybeManagedPtr(rawPtr); + + EXPECT_NE(maybeManagedPtr, nullptr); + + containedObject->checkpoint(); + } + + // ensure object is still alive after scope exit + containedObject->checkpoint(); + delete containedObject; +} + +/** + * Test behavior of MaybeManagedPtr when using smart pointer at construction. We + * expect the contained object to be deallocated when the MaybeManagedPtr goes + * out of scope. + */ +TEST(MaybeManagedPtrBehaviourTest, SharedPointer) { + ContainedObjectMock* containedObject{new ContainedObjectMock()}; + + /** + * We call checkpoint once in enclosed scope to ensure + * contained object is still alive after scope exit. We expect destructor to + * be called once to verify object destruction. Note that we do not explicitly + * delete the contained object here, since maybeManagedPtr will take care of + * this for us on scope exit. + */ + testing::InSequence s; + EXPECT_CALL(*containedObject, checkpoint).Times(2); + EXPECT_CALL(*containedObject, dtor).Times(1); + + { + auto maybeManagedPtr = folly::MaybeManagedPtr(nullptr); + + { + auto sharedPtr = std::shared_ptr(containedObject); + EXPECT_EQ(sharedPtr.use_count(), 1); + + maybeManagedPtr = folly::MaybeManagedPtr(sharedPtr); + EXPECT_EQ(sharedPtr.use_count(), 2); + EXPECT_NE(maybeManagedPtr, nullptr); + EXPECT_EQ(maybeManagedPtr, sharedPtr); + EXPECT_EQ(maybeManagedPtr.useCount(), 2); + } + + /** + * Contained object is still alive, even though we destroyed the initial + * shared pointer. Destructor has not been called yet since this would + * violate required sequencing. + */ + containedObject->checkpoint(); + EXPECT_EQ(maybeManagedPtr.useCount(), 1); + + /** + * Contained object is still alive, but will be destroyed by maybeManagedPtr + * on scope exit. + */ + containedObject->checkpoint(); + } +}