-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
a339306
commit 772ca51
Showing
2 changed files
with
372 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <memory> | ||
|
||
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 <typename T> | ||
class MaybeManagedPtr { | ||
public: | ||
/* implicit */ MaybeManagedPtr(T* t) | ||
: t_(std::shared_ptr<T>(std::shared_ptr<void>(), t)) {} | ||
/* implicit */ MaybeManagedPtr(std::shared_ptr<T> 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<T>& 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<T>& 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> t_; | ||
}; | ||
|
||
} // namespace folly |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <folly/MaybeManagedPtr.h> | ||
#include <folly/portability/GMock.h> | ||
#include <folly/portability/GTest.h> | ||
|
||
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 <typename T> | ||
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<T, int*>::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<int*, std::shared_ptr<int>>; | ||
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<int>(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<int>(this->somePtr_); | ||
|
||
EXPECT_EQ(maybeManagedPtr.get(), this->intPtr_); | ||
EXPECT_FALSE( | ||
std::is_const_v<std::remove_pointer_t<decltype(maybeManagedPtr.get())>>); | ||
} | ||
|
||
/** | ||
* Test that member of pointer operator -> returns a pointer to the same | ||
* address. | ||
*/ | ||
TYPED_TEST(MaybeManagedPtrInterfaceTest, MemberOfPointerOperator) { | ||
auto maybeManagedPtr = folly::MaybeManagedPtr<int>(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<int>(this->somePtr_); | ||
|
||
EXPECT_EQ(&(*maybeManagedPtr), this->intPtr_); | ||
} | ||
|
||
/** | ||
* Test equal to operator with raw pointers. | ||
*/ | ||
TYPED_TEST(MaybeManagedPtrInterfaceTest, EqualToOperatorRawPointer) { | ||
auto maybeManagedPtr = folly::MaybeManagedPtr<int>(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<int>(this->somePtr_); | ||
|
||
// prevent second shared pointer from deallocating value | ||
auto sameSharedPtr = | ||
std::shared_ptr<int>(std::shared_ptr<void>(), this->intPtr_); | ||
|
||
auto otherSharedPtr = std::shared_ptr<int>(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<int>(this->somePtr_); | ||
|
||
auto sameMaybeManagedPtr = folly::MaybeManagedPtr<int>(this->somePtr_); | ||
auto otherMaybeManagedPtr = | ||
folly::MaybeManagedPtr<int>(std::shared_ptr<int>(new int(kTestIntValue))); | ||
|
||
EXPECT_EQ(maybeManagedPtr, sameMaybeManagedPtr); | ||
EXPECT_NE(maybeManagedPtr, otherMaybeManagedPtr); | ||
} | ||
|
||
/** | ||
* Test bool type conversion operator | ||
*/ | ||
TYPED_TEST(MaybeManagedPtrInterfaceTest, BoolTypeConversionOperator) { | ||
auto maybeManagedPtr = folly::MaybeManagedPtr<int>(nullptr); | ||
EXPECT_FALSE(maybeManagedPtr); | ||
|
||
maybeManagedPtr = folly::MaybeManagedPtr<int>(this->somePtr_); | ||
EXPECT_TRUE(maybeManagedPtr); | ||
} | ||
|
||
/** | ||
* Test implicit type conversion operator | ||
*/ | ||
TYPED_TEST(MaybeManagedPtrInterfaceTest, ImplicitTypeConversionOperator) { | ||
auto maybeManagedPtr = folly::MaybeManagedPtr<int>(this->somePtr_); | ||
|
||
int* rawPtr = maybeManagedPtr; | ||
EXPECT_EQ(rawPtr, this->intPtr_); | ||
} | ||
|
||
/** | ||
* Test explicit type conversion operator | ||
*/ | ||
TYPED_TEST(MaybeManagedPtrInterfaceTest, ExplicitTypeConversionOperator) { | ||
auto maybeManagedPtr = folly::MaybeManagedPtr<int>(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<ContainedObjectMock>(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<ContainedObjectMock>(nullptr); | ||
|
||
{ | ||
auto sharedPtr = std::shared_ptr<ContainedObjectMock>(containedObject); | ||
EXPECT_EQ(sharedPtr.use_count(), 1); | ||
|
||
maybeManagedPtr = folly::MaybeManagedPtr<ContainedObjectMock>(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(); | ||
} | ||
} |