Allow Ref<Derived> -> Ref<Base> assignment, and move operations

Previous to this change, you were unable to assign or move Ref<Derived>
to a Ref<Base>.

This change addresses the problem by introducing <typename U> versions
of assignment, copy and move methods. nullptr_t specific ones were
also added to disambiguate things for the compiler.

Bug:dawn:390
Change-Id: Ib5d44231e26db35de33d63c67b36b5bf411a3540
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/20121
Commit-Queue: Rafael Cintron <rafael.cintron@microsoft.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Rafael Cintron 2020-04-22 20:34:00 +00:00 committed by Commit Bot service account
parent d4302437c9
commit b4eccc8227
2 changed files with 208 additions and 9 deletions

View File

@ -41,16 +41,32 @@ class RefCounted {
template <typename T> template <typename T>
class Ref { class Ref {
public: public:
Ref() { Ref() = default;
constexpr Ref(std::nullptr_t) {
} }
Ref(T* p) : mPointee(p) { Ref& operator=(std::nullptr_t) {
Release();
mPointee = nullptr;
return *this;
}
template <typename U>
Ref(U* p) : mPointee(p) {
static_assert(std::is_convertible<U*, T*>::value, "");
Reference(); Reference();
} }
Ref(const Ref<T>& other) : mPointee(other.mPointee) { Ref(const Ref<T>& other) : mPointee(other.mPointee) {
Reference(); Reference();
} }
template <typename U>
Ref(const Ref<U>& other) : mPointee(other.mPointee) {
static_assert(std::is_convertible<U*, T*>::value, "");
Reference();
}
Ref<T>& operator=(const Ref<T>& other) { Ref<T>& operator=(const Ref<T>& other) {
if (&other == this) if (&other == this)
return *this; return *this;
@ -62,10 +78,24 @@ class Ref {
return *this; return *this;
} }
Ref(Ref<T>&& other) { template <typename U>
Ref<T>& operator=(const Ref<U>& other) {
static_assert(std::is_convertible<U*, T*>::value, "");
other.Reference();
Release();
mPointee = other.mPointee;
return *this;
}
template <typename U>
Ref(Ref<U>&& other) {
static_assert(std::is_convertible<U*, T*>::value, "");
mPointee = other.mPointee; mPointee = other.mPointee;
other.mPointee = nullptr; other.mPointee = nullptr;
} }
Ref<T>& operator=(Ref<T>&& other) { Ref<T>& operator=(Ref<T>&& other) {
if (&other == this) if (&other == this)
return *this; return *this;
@ -76,6 +106,16 @@ class Ref {
return *this; return *this;
} }
template <typename U>
Ref<T>& operator=(Ref<U>&& other) {
static_assert(std::is_convertible<U*, T*>::value, "");
Release();
mPointee = other.mPointee;
other.mPointee = nullptr;
return *this;
}
~Ref() { ~Ref() {
Release(); Release();
@ -114,6 +154,11 @@ class Ref {
} }
private: private:
// Friend is needed so that instances of Ref<U> can assign mPointee
// members of Ref<T>.
template <typename U>
friend class Ref;
void Reference() const { void Reference() const {
if (mPointee != nullptr) { if (mPointee != nullptr) {
mPointee->Reference(); mPointee->Reference();
@ -125,7 +170,6 @@ class Ref {
} }
} }
// static_assert(std::is_base_of<RefCounted, T>::value, "");
T* mPointee = nullptr; T* mPointee = nullptr;
}; };

View File

@ -17,19 +17,20 @@
#include "common/RefCounted.h" #include "common/RefCounted.h"
struct RCTest : public RefCounted { class RCTest : public RefCounted {
public:
RCTest() : RefCounted() { RCTest() : RefCounted() {
} }
RCTest(uint64_t payload) : RefCounted(payload) { RCTest(uint64_t payload) : RefCounted(payload) {
} }
RCTest(bool* deleted) : deleted(deleted) { RCTest(bool* deleted) : mDeleted(deleted) {
} }
~RCTest() override { ~RCTest() override {
if (deleted != nullptr) { if (mDeleted != nullptr) {
*deleted = true; *mDeleted = true;
} }
} }
@ -37,7 +38,19 @@ struct RCTest : public RefCounted {
return this; return this;
} }
bool* deleted = nullptr; private:
bool* mDeleted = nullptr;
};
struct RCTestDerived : public RCTest {
RCTestDerived() : RCTest() {
}
RCTestDerived(uint64_t payload) : RCTest(payload) {
}
RCTestDerived(bool* deleted) : RCTest(deleted) {
}
}; };
// Test that RCs start with one ref, and removing it destroys the object. // Test that RCs start with one ref, and removing it destroys the object.
@ -142,14 +155,21 @@ TEST(Ref, CopyConstructor) {
RCTest* original = new RCTest(&deleted); RCTest* original = new RCTest(&deleted);
Ref<RCTest> source(original); Ref<RCTest> source(original);
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
Ref<RCTest> destination(source); Ref<RCTest> destination(source);
EXPECT_EQ(original->GetRefCountForTesting(), 3u);
original->Release(); original->Release();
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
EXPECT_EQ(source.Get(), original); EXPECT_EQ(source.Get(), original);
EXPECT_EQ(destination.Get(), original); EXPECT_EQ(destination.Get(), original);
source = nullptr; source = nullptr;
EXPECT_FALSE(deleted); EXPECT_FALSE(deleted);
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
destination = nullptr; destination = nullptr;
EXPECT_TRUE(deleted); EXPECT_TRUE(deleted);
} }
@ -182,8 +202,13 @@ TEST(Ref, MoveConstructor) {
RCTest* original = new RCTest(&deleted); RCTest* original = new RCTest(&deleted);
Ref<RCTest> source(original); Ref<RCTest> source(original);
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
Ref<RCTest> destination(std::move(source)); Ref<RCTest> destination(std::move(source));
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
original->Release(); original->Release();
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
EXPECT_EQ(source.Get(), nullptr); EXPECT_EQ(source.Get(), nullptr);
EXPECT_EQ(destination.Get(), original); EXPECT_EQ(destination.Get(), original);
@ -199,10 +224,14 @@ TEST(Ref, MoveAssignment) {
RCTest* original = new RCTest(&deleted); RCTest* original = new RCTest(&deleted);
Ref<RCTest> source(original); Ref<RCTest> source(original);
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
original->Release(); original->Release();
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
Ref<RCTest> destination; Ref<RCTest> destination;
destination = std::move(source); destination = std::move(source);
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
EXPECT_EQ(source.Get(), nullptr); EXPECT_EQ(source.Get(), nullptr);
EXPECT_EQ(destination.Get(), original); EXPECT_EQ(destination.Get(), original);
@ -212,6 +241,31 @@ TEST(Ref, MoveAssignment) {
EXPECT_TRUE(deleted); EXPECT_TRUE(deleted);
} }
// Test move assigment where the destination and source
// point to the same underlying object.
TEST(Ref, MoveAssignmentSameObject) {
bool deleted = false;
RCTest* original = new RCTest(&deleted);
Ref<RCTest> source(original);
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
original->Release();
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
Ref<RCTest>& referenceToSource = source;
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
referenceToSource = std::move(source);
EXPECT_EQ(source.Get(), original);
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
EXPECT_FALSE(deleted);
source = nullptr;
EXPECT_TRUE(deleted);
}
// Test the payload initial value is set correctly // Test the payload initial value is set correctly
TEST(Ref, InitialPayloadValue) { TEST(Ref, InitialPayloadValue) {
RCTest* testDefaultConstructor = new RCTest(); RCTest* testDefaultConstructor = new RCTest();
@ -256,3 +310,104 @@ TEST(Ref, Detach) {
detached->Release(); detached->Release();
EXPECT_TRUE(deleted); EXPECT_TRUE(deleted);
} }
// Test constructor passed a derived pointer
TEST(Ref, DerivedPointerConstructor) {
bool deleted = false;
{
Ref<RCTest> test(new RCTestDerived(&deleted));
test->Release();
}
EXPECT_TRUE(deleted);
}
// Test copy constructor of derived class
TEST(Ref, DerivedCopyConstructor) {
bool deleted = false;
Ref<RCTestDerived> testDerived(new RCTestDerived(&deleted));
testDerived->Release();
{
Ref<RCTest> testBase(testDerived);
EXPECT_EQ(testBase->GetRefCountForTesting(), 2u);
EXPECT_EQ(testDerived->GetRefCountForTesting(), 2u);
}
EXPECT_EQ(testDerived->GetRefCountForTesting(), 1u);
}
// Test Ref constructed with nullptr
TEST(Ref, ConstructedWithNullptr) {
Ref<RCTest> test(nullptr);
EXPECT_EQ(test.Get(), nullptr);
}
// Test Ref's copy assignment with derived class
TEST(Ref, CopyAssignmentDerived) {
bool deleted = false;
RCTestDerived* original = new RCTestDerived(&deleted);
Ref<RCTestDerived> source(original);
original->Release();
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
Ref<RCTest> destination;
destination = source;
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
EXPECT_EQ(source.Get(), original);
EXPECT_EQ(destination.Get(), original);
source = nullptr;
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
EXPECT_FALSE(deleted);
destination = nullptr;
EXPECT_TRUE(deleted);
}
// Test Ref's move constructor with derived class
TEST(Ref, MoveConstructorDerived) {
bool deleted = false;
RCTestDerived* original = new RCTestDerived(&deleted);
Ref<RCTestDerived> source(original);
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
Ref<RCTest> destination(std::move(source));
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
original->Release();
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
EXPECT_EQ(source.Get(), nullptr);
EXPECT_EQ(destination.Get(), original);
EXPECT_FALSE(deleted);
destination = nullptr;
EXPECT_TRUE(deleted);
}
// Test Ref's move assignment with derived class
TEST(Ref, MoveAssignmentDerived) {
bool deleted = false;
RCTestDerived* original = new RCTestDerived(&deleted);
Ref<RCTestDerived> source(original);
EXPECT_EQ(original->GetRefCountForTesting(), 2u);
original->Release();
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
Ref<RCTest> destination;
destination = std::move(source);
EXPECT_EQ(original->GetRefCountForTesting(), 1u);
EXPECT_EQ(source.Get(), nullptr);
EXPECT_EQ(destination.Get(), original);
EXPECT_FALSE(deleted);
destination = nullptr;
EXPECT_TRUE(deleted);
}