| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // This file tests both ref_counted.h and ref_ptr.h (which the former includes). | |
| // TODO(vtl): Possibly we could separate these tests out better, since a lot of | |
| // it is actually testing |RefPtr|. | |
| #include "flutter/fml/memory/ref_counted.h" | |
| #include "flutter/fml/macros.h" | |
| #include "gtest/gtest.h" | |
| #if defined(__clang__) | |
| #define ALLOW_PESSIMIZING_MOVE(code_line) \ | |
| _Pragma("clang diagnostic push") \ | |
| _Pragma("clang diagnostic ignored \"-Wpessimizing-move\"") code_line; \ | |
| _Pragma("clang diagnostic pop") | |
| #else | |
| #define ALLOW_PESSIMIZING_MOVE(code_line) code_line; | |
| #endif | |
| #if defined(__clang__) | |
| #define ALLOW_SELF_MOVE(code_line) \ | |
| _Pragma("clang diagnostic push") \ | |
| _Pragma("clang diagnostic ignored \"-Wself-move\"") code_line; \ | |
| _Pragma("clang diagnostic pop") | |
| #else | |
| #define ALLOW_SELF_MOVE(code_line) code_line; | |
| #endif | |
| #if defined(__clang__) | |
| #define ALLOW_SELF_ASSIGN_OVERLOADED(code_line) \ | |
| _Pragma("clang diagnostic push") \ | |
| _Pragma("clang diagnostic ignored \"-Wself-assign-overloaded\"") \ | |
| code_line; \ | |
| _Pragma("clang diagnostic pop") | |
| #else | |
| #define ALLOW_SELF_ASSIGN_OVERLOADED(code_line) code_line; | |
| #endif | |
| namespace fml { | |
| namespace { | |
| class MyClass : public RefCountedThreadSafe<MyClass> { | |
| protected: | |
| MyClass(MyClass** created, bool* was_destroyed) | |
| : was_destroyed_(was_destroyed) { | |
| if (created) { | |
| *created = this; | |
| } | |
| } | |
| virtual ~MyClass() { | |
| if (was_destroyed_) { | |
| *was_destroyed_ = true; | |
| } | |
| } | |
| private: | |
| FML_FRIEND_REF_COUNTED_THREAD_SAFE(MyClass); | |
| FML_FRIEND_MAKE_REF_COUNTED(MyClass); | |
| bool* was_destroyed_; | |
| FML_DISALLOW_COPY_AND_ASSIGN(MyClass); | |
| }; | |
| class MySubclass final : public MyClass { | |
| private: | |
| FML_FRIEND_REF_COUNTED_THREAD_SAFE(MySubclass); | |
| FML_FRIEND_MAKE_REF_COUNTED(MySubclass); | |
| MySubclass(MySubclass** created, bool* was_destroyed) | |
| : MyClass(nullptr, was_destroyed) { | |
| if (created) { | |
| *created = this; | |
| } | |
| } | |
| ~MySubclass() override {} | |
| FML_DISALLOW_COPY_AND_ASSIGN(MySubclass); | |
| }; | |
| TEST(RefCountedTest, Constructors) { | |
| bool was_destroyed; | |
| { | |
| // Default. | |
| RefPtr<MyClass> r; | |
| EXPECT_TRUE(r.get() == nullptr); | |
| EXPECT_FALSE(r); | |
| } | |
| { | |
| // Nullptr. | |
| RefPtr<MyClass> r(nullptr); | |
| EXPECT_TRUE(r.get() == nullptr); | |
| EXPECT_FALSE(r); | |
| } | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| // Adopt, then RVO. | |
| RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed)); | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(created, r.get()); | |
| EXPECT_TRUE(r); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| // Adopt, then move. | |
| ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r( | |
| std::move(MakeRefCounted<MyClass>(&created, &was_destroyed)))) | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(created, r.get()); | |
| EXPECT_TRUE(r); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed)); | |
| // Copy. | |
| // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) | |
| RefPtr<MyClass> r2(r1); | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(created, r1.get()); | |
| EXPECT_EQ(created, r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed)); | |
| // From raw pointer. | |
| RefPtr<MyClass> r2(created); | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(created, r1.get()); | |
| EXPECT_EQ(created, r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MySubclass* created = nullptr; | |
| was_destroyed = false; | |
| // Adopt, then "move". | |
| RefPtr<MyClass> r(MakeRefCounted<MySubclass>(&created, &was_destroyed)); | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(static_cast<MyClass*>(created), r.get()); | |
| EXPECT_TRUE(r); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MySubclass* created = nullptr; | |
| was_destroyed = false; | |
| // Adopt, then "move". | |
| ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r( | |
| std::move(MakeRefCounted<MySubclass>(&created, &was_destroyed)))) | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(static_cast<MyClass*>(created), r.get()); | |
| EXPECT_TRUE(r); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MySubclass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed)); | |
| // "Copy". | |
| RefPtr<MyClass> r2(r1); | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(static_cast<MyClass*>(created), r1.get()); | |
| EXPECT_EQ(static_cast<MyClass*>(created), r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MySubclass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed)); | |
| // From raw pointer. | |
| RefPtr<MyClass> r2(created); | |
| EXPECT_TRUE(created); | |
| EXPECT_EQ(static_cast<MyClass*>(created), r1.get()); | |
| EXPECT_EQ(static_cast<MyClass*>(created), r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| } | |
| TEST(RefCountedTest, NullAssignmentToNull) { | |
| RefPtr<MyClass> r1; | |
| // No-op null assignment using |nullptr|. | |
| r1 = nullptr; | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| RefPtr<MyClass> r2; | |
| // No-op null assignment using copy constructor. | |
| r1 = r2; | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_TRUE(r2.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r2); | |
| // No-op null assignment using move constructor. | |
| r1 = std::move(r2); | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| // The clang linter flags the method called on the moved-from reference, but | |
| // this is testing the move implementation, so it is marked NOLINT. | |
| EXPECT_TRUE(r2.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move, | |
| // bugprone-use-after-move) | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r2); | |
| RefPtr<MySubclass> r3; | |
| // No-op null assignment using "copy" constructor. | |
| r1 = r3; | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_TRUE(r3.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r3); | |
| // No-op null assignment using "move" constructor. | |
| r1 = std::move(r3); | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_TRUE(r3.get() == nullptr); // NOLINT(bugprone-use-after-move) | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r3); | |
| } | |
| TEST(RefCountedTest, NonNullAssignmentToNull) { | |
| bool was_destroyed; | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed)); | |
| RefPtr<MyClass> r2; | |
| // Copy assignment (to null ref pointer). | |
| r2 = r1; | |
| EXPECT_EQ(created, r1.get()); | |
| EXPECT_EQ(created, r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed)); | |
| RefPtr<MyClass> r2; | |
| // Move assignment (to null ref pointer). | |
| r2 = std::move(r1); | |
| // The clang linter flags the method called on the moved-from reference, but | |
| // this is testing the move implementation, so it is marked NOLINT. | |
| EXPECT_TRUE(r1.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move, | |
| // bugprone-use-after-move) | |
| EXPECT_EQ(created, r2.get()); | |
| EXPECT_FALSE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MySubclass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed)); | |
| RefPtr<MyClass> r2; | |
| // "Copy" assignment (to null ref pointer). | |
| r2 = r1; | |
| EXPECT_EQ(created, r1.get()); | |
| EXPECT_EQ(static_cast<MyClass*>(created), r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MySubclass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed)); | |
| RefPtr<MyClass> r2; | |
| // "Move" assignment (to null ref pointer). | |
| r2 = std::move(r1); | |
| EXPECT_TRUE(r1.get() == nullptr); // NOLINT(bugprone-use-after-move) | |
| EXPECT_EQ(static_cast<MyClass*>(created), r2.get()); | |
| EXPECT_FALSE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| } | |
| TEST(RefCountedTest, NullAssignmentToNonNull) { | |
| bool was_destroyed = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed)); | |
| // Null assignment (to non-null ref pointer) using |nullptr|. | |
| r1 = nullptr; | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| EXPECT_TRUE(was_destroyed); | |
| was_destroyed = false; | |
| r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed); | |
| RefPtr<MyClass> r2; | |
| // Null assignment (to non-null ref pointer) using copy constructor. | |
| r1 = r2; | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_TRUE(r2.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r2); | |
| EXPECT_TRUE(was_destroyed); | |
| was_destroyed = false; | |
| r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed); | |
| // Null assignment using move constructor. | |
| r1 = std::move(r2); | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| // The clang linter flags the method called on the moved-from reference, but | |
| // this is testing the move implementation, so it is marked NOLINT. | |
| EXPECT_TRUE(r2.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move, | |
| // bugprone-use-after-move) | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r2); | |
| EXPECT_TRUE(was_destroyed); | |
| was_destroyed = false; | |
| r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed); | |
| RefPtr<MySubclass> r3; | |
| // Null assignment (to non-null ref pointer) using "copy" constructor. | |
| r1 = r3; | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_TRUE(r3.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r3); | |
| EXPECT_TRUE(was_destroyed); | |
| was_destroyed = false; | |
| r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed); | |
| // Null assignment (to non-null ref pointer) using "move" constructor. | |
| r1 = std::move(r3); | |
| EXPECT_TRUE(r1.get() == nullptr); | |
| EXPECT_TRUE(r3.get() == nullptr); // NOLINT(bugprone-use-after-move) | |
| EXPECT_FALSE(r1); | |
| EXPECT_FALSE(r3); | |
| EXPECT_TRUE(was_destroyed); | |
| } | |
| TEST(RefCountedTest, NonNullAssignmentToNonNull) { | |
| bool was_destroyed1; | |
| bool was_destroyed2; | |
| { | |
| was_destroyed1 = false; | |
| was_destroyed2 = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1)); | |
| RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2)); | |
| // Copy assignment (to non-null ref pointer). | |
| r2 = r1; | |
| EXPECT_EQ(r1.get(), r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed1); | |
| EXPECT_TRUE(was_destroyed2); | |
| } | |
| EXPECT_TRUE(was_destroyed1); | |
| { | |
| was_destroyed1 = false; | |
| was_destroyed2 = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1)); | |
| RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2)); | |
| // Move assignment (to non-null ref pointer). | |
| r2 = std::move(r1); | |
| // The clang linter flags the method called on the moved-from reference, but | |
| // this is testing the move implementation, so it is marked NOLINT. | |
| EXPECT_TRUE(r1.get() == nullptr); // NOLINT(clang-analyzer-cplusplus.Move, | |
| // bugprone-use-after-move) | |
| EXPECT_FALSE(r2.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed1); | |
| EXPECT_TRUE(was_destroyed2); | |
| } | |
| EXPECT_TRUE(was_destroyed1); | |
| { | |
| was_destroyed1 = false; | |
| was_destroyed2 = false; | |
| RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1)); | |
| RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2)); | |
| // "Copy" assignment (to non-null ref pointer). | |
| r2 = r1; | |
| EXPECT_EQ(r1.get(), r2.get()); | |
| EXPECT_TRUE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed1); | |
| EXPECT_TRUE(was_destroyed2); | |
| } | |
| EXPECT_TRUE(was_destroyed1); | |
| { | |
| was_destroyed1 = false; | |
| was_destroyed2 = false; | |
| RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1)); | |
| RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2)); | |
| // Move assignment (to non-null ref pointer). | |
| r2 = std::move(r1); | |
| EXPECT_TRUE(r1.get() == nullptr); // NOLINT(bugprone-use-after-move) | |
| EXPECT_FALSE(r2.get() == nullptr); | |
| EXPECT_FALSE(r1); | |
| EXPECT_TRUE(r2); | |
| EXPECT_FALSE(was_destroyed1); | |
| EXPECT_TRUE(was_destroyed2); | |
| } | |
| EXPECT_TRUE(was_destroyed1); | |
| } | |
| TEST(RefCountedTest, SelfAssignment) { | |
| bool was_destroyed; | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| // This line is marked NOLINT because the clang linter does not reason about | |
| // the value of the reference count. In particular, the self-assignment | |
| // below is handled in the copy constructor by a refcount increment then | |
| // decrement. The linter sees only that the decrement might destroy the | |
| // object. | |
| RefPtr<MyClass> r(MakeRefCounted<MyClass>( // NOLINT | |
| &created, &was_destroyed)); | |
| // Copy. | |
| ALLOW_SELF_ASSIGN_OVERLOADED(r = r); | |
| EXPECT_EQ(created, r.get()); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| { | |
| MyClass* created = nullptr; | |
| was_destroyed = false; | |
| RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed)); | |
| // Move. | |
| ALLOW_SELF_MOVE(r = std::move(r)) | |
| EXPECT_EQ(created, r.get()); | |
| EXPECT_FALSE(was_destroyed); | |
| } | |
| EXPECT_TRUE(was_destroyed); | |
| } | |
| TEST(RefCountedTest, Swap) { | |
| MyClass* created1 = nullptr; | |
| static bool was_destroyed1; | |
| was_destroyed1 = false; | |
| RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created1, &was_destroyed1)); | |
| EXPECT_TRUE(created1); | |
| EXPECT_EQ(created1, r1.get()); | |
| MyClass* created2 = nullptr; | |
| static bool was_destroyed2; | |
| was_destroyed2 = false; | |
| RefPtr<MyClass> r2(MakeRefCounted<MyClass>(&created2, &was_destroyed2)); | |
| EXPECT_TRUE(created2); | |
| EXPECT_EQ(created2, r2.get()); | |
| EXPECT_NE(created1, created2); | |
| r1.swap(r2); | |
| EXPECT_EQ(created2, r1.get()); | |
| EXPECT_EQ(created1, r2.get()); | |
| } | |
| TEST(RefCountedTest, GetAndDereferenceOperators) { | |
| // Note: We check here that .get(), operator*, and operator-> are const, but | |
| // return non-const pointers/refs. | |
| MyClass* created = nullptr; | |
| const RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, nullptr)); | |
| MyClass* ptr = r.get(); // Assign to non-const pointer. | |
| EXPECT_EQ(created, ptr); | |
| ptr = r.operator->(); // Assign to non-const pointer. | |
| EXPECT_EQ(created, ptr); | |
| MyClass& ref = *r; // "Assign" to non-const reference. | |
| EXPECT_EQ(created, &ref); | |
| } | |
| // You can manually call |AddRef()| and |Release()| if you want. | |
| TEST(RefCountedTest, AddRefRelease) { | |
| MyClass* created = nullptr; | |
| bool was_destroyed = false; | |
| { | |
| RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed)); | |
| EXPECT_EQ(created, r.get()); | |
| created->AddRef(); | |
| } | |
| EXPECT_FALSE(was_destroyed); | |
| created->Release(); | |
| EXPECT_TRUE(was_destroyed); | |
| } | |
| TEST(RefCountedTest, Mix) { | |
| MySubclass* created = nullptr; | |
| bool was_destroyed = false; | |
| RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed)); | |
| ASSERT_FALSE(was_destroyed); | |
| EXPECT_TRUE(created->HasOneRef()); | |
| created->AssertHasOneRef(); | |
| RefPtr<MySubclass> r2 = r1; | |
| ASSERT_FALSE(was_destroyed); | |
| EXPECT_FALSE(created->HasOneRef()); | |
| r1 = nullptr; | |
| ASSERT_FALSE(was_destroyed); | |
| created->AssertHasOneRef(); | |
| { | |
| RefPtr<MyClass> r3 = r2; | |
| EXPECT_FALSE(created->HasOneRef()); | |
| { | |
| // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) | |
| RefPtr<MyClass> r4(r3); | |
| r2 = nullptr; | |
| ASSERT_FALSE(was_destroyed); | |
| EXPECT_FALSE(created->HasOneRef()); | |
| } | |
| ASSERT_FALSE(was_destroyed); | |
| EXPECT_TRUE(created->HasOneRef()); | |
| created->AssertHasOneRef(); | |
| r1 = RefPtr<MySubclass>(static_cast<MySubclass*>(r3.get())); | |
| ASSERT_FALSE(was_destroyed); | |
| EXPECT_FALSE(created->HasOneRef()); | |
| } | |
| ASSERT_FALSE(was_destroyed); | |
| EXPECT_TRUE(created->HasOneRef()); | |
| created->AssertHasOneRef(); | |
| EXPECT_EQ(created, r1.get()); | |
| r1 = nullptr; | |
| EXPECT_TRUE(was_destroyed); | |
| } | |
| class MyPublicClass : public RefCountedThreadSafe<MyPublicClass> { | |
| public: | |
| // Overloaded constructors work with |MakeRefCounted()|. | |
| MyPublicClass() : has_num_(false), num_(0) {} | |
| explicit MyPublicClass(int num) : has_num_(true), num_(num) {} | |
| ~MyPublicClass() {} | |
| bool has_num() const { return has_num_; } | |
| int num() const { return num_; } | |
| private: | |
| bool has_num_; | |
| int num_; | |
| FML_DISALLOW_COPY_AND_ASSIGN(MyPublicClass); | |
| }; | |
| // You can also just keep constructors and destructors public. Make sure that | |
| // works (mostly that it compiles). | |
| TEST(RefCountedTest, PublicCtorAndDtor) { | |
| RefPtr<MyPublicClass> r1 = MakeRefCounted<MyPublicClass>(); | |
| ASSERT_TRUE(r1); | |
| EXPECT_FALSE(r1->has_num()); | |
| RefPtr<MyPublicClass> r2 = MakeRefCounted<MyPublicClass>(123); | |
| ASSERT_TRUE(r2); | |
| EXPECT_TRUE(r2->has_num()); | |
| EXPECT_EQ(123, r2->num()); | |
| EXPECT_NE(r1.get(), r2.get()); | |
| r1 = r2; | |
| EXPECT_TRUE(r1->has_num()); | |
| EXPECT_EQ(123, r1->num()); | |
| EXPECT_EQ(r1.get(), r2.get()); | |
| r2 = nullptr; | |
| EXPECT_FALSE(r2); | |
| EXPECT_TRUE(r1->has_num()); | |
| EXPECT_EQ(123, r1->num()); | |
| r1 = nullptr; | |
| EXPECT_FALSE(r1); | |
| } | |
| // The danger with having a public constructor or destructor is that certain | |
| // things will compile. You should get some protection by assertions in Debug | |
| // builds. | |
| #ifndef NDEBUG | |
| TEST(RefCountedTest, DebugChecks) { | |
| { | |
| MyPublicClass* p = new MyPublicClass(); | |
| EXPECT_DEATH_IF_SUPPORTED( // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) | |
| delete p, "!adoption_required_"); | |
| } | |
| { | |
| MyPublicClass* p = new MyPublicClass(); | |
| EXPECT_DEATH_IF_SUPPORTED( // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) | |
| RefPtr<MyPublicClass> r(p), "!adoption_required_"); | |
| } | |
| { | |
| RefPtr<MyPublicClass> r(MakeRefCounted<MyPublicClass>()); | |
| EXPECT_DEATH_IF_SUPPORTED(delete r.get(), "destruction_started_"); | |
| } | |
| } | |
| #endif | |
| // TODO(vtl): Add (threaded) stress tests. | |
| } // namespace | |
| } // namespace fml |