Skip to content

Commit

Permalink
MaybeManagedPtr
Browse files Browse the repository at this point in the history
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
Timm Böttger authored and facebook-github-bot committed Oct 30, 2023
1 parent a339306 commit 772ca51
Show file tree
Hide file tree
Showing 2 changed files with 372 additions and 0 deletions.
120 changes: 120 additions & 0 deletions folly/MaybeManagedPtr.h
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
252 changes: 252 additions & 0 deletions folly/test/MaybeManagedPtrTest.cpp
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();
}
}

0 comments on commit 772ca51

Please sign in to comment.