Commit f35a483b authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

heap: Fix collections initializations

- Fix various operator new invocations.
- Disallow nesting of HeapLinkedHashSet which depends on the fact that
  the collection itself does not move.

Change-Id: I37978a0ea226ab40227caaff0246e3c70f47b022
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1660556
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#669582}
parent e3314ef9
......@@ -22,7 +22,7 @@ namespace {
using ActiveScrollTimelineSet = HeapHashCountedSet<WeakMember<Node>>;
ActiveScrollTimelineSet& GetActiveScrollTimelineSet() {
DEFINE_STATIC_LOCAL(Persistent<ActiveScrollTimelineSet>, set,
(new ActiveScrollTimelineSet));
(MakeGarbageCollected<ActiveScrollTimelineSet>()));
return *set;
}
......
......@@ -131,14 +131,15 @@ using DOMWindowSet = HeapHashCountedSet<WeakMember<LocalDOMWindow>>;
static DOMWindowSet& WindowsWithUnloadEventListeners() {
DEFINE_STATIC_LOCAL(Persistent<DOMWindowSet>,
windows_with_unload_event_listeners, (new DOMWindowSet));
windows_with_unload_event_listeners,
(MakeGarbageCollected<DOMWindowSet>()));
return *windows_with_unload_event_listeners;
}
static DOMWindowSet& WindowsWithBeforeUnloadEventListeners() {
DEFINE_STATIC_LOCAL(Persistent<DOMWindowSet>,
windows_with_before_unload_event_listeners,
(new DOMWindowSet));
(MakeGarbageCollected<DOMWindowSet>()));
return *windows_with_before_unload_event_listeners;
}
......
......@@ -5,6 +5,8 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_HEAP_ALLOCATOR_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_HEAP_ALLOCATOR_H_
#include <type_traits>
#include "build/build_config.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/heap_buildflags.h"
......@@ -27,6 +29,22 @@
namespace blink {
#define DISALLOW_IN_CONTAINER() \
public: \
using IsDisallowedInContainerMarker = int; \
\
private: \
friend class ::WTF::internal::__thisIsHereToForceASemicolonAfterThisMacro
// IsAllowedInContainer returns true if some type T supports being nested
// arbitrarily in other containers. This is relevant for collections where some
// collections assume that they are placed on a non-moving arena.
template <typename T, typename = int>
struct IsAllowedInContainer : std::true_type {};
template <typename T>
struct IsAllowedInContainer<T, typename T::IsDisallowedInContainerMarker>
: std::false_type {};
template <typename T, typename Traits = WTF::VectorTraits<T>>
class HeapVectorBacking {
DISALLOW_NEW();
......@@ -457,12 +475,20 @@ class HeapHashMap : public HashMap<KeyArg,
MappedTraitsArg,
HeapAllocator> {
IS_GARBAGE_COLLECTED_TYPE();
using Base =
HashMap<KeyArg, MappedArg, HashArg, KeyTraitsArg, MappedTraitsArg>;
static_assert(WTF::IsTraceable<KeyArg>::value ||
WTF::IsTraceable<MappedArg>::value,
"For hash maps without traceable elements, use HashMap<> "
"instead of HeapHashMap<>");
DISALLOW_NEW();
static void CheckType() {
static_assert(
IsAllowedInContainer<KeyArg>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(
IsAllowedInContainer<MappedArg>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(
WTF::IsTraceable<KeyArg>::value || WTF::IsTraceable<MappedArg>::value,
"For hash maps without traceable elements, use HashMap<> "
"instead of HeapHashMap<>");
}
public:
static void* AllocateObject(size_t size, bool eagerly_sweep) {
......@@ -471,16 +497,7 @@ class HeapHashMap : public HashMap<KeyArg,
size, eagerly_sweep);
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
void* operator new[](size_t size) = delete;
void operator delete[](void* p) = delete;
void* operator new(size_t size, NotNullTag null_tag, void* location) {
return Base::operator new(size, null_tag, location);
}
void* operator new(size_t size, void* location) {
return Base::operator new(size, location);
}
HeapHashMap() { CheckType(); }
};
template <typename ValueArg,
......@@ -489,10 +506,16 @@ template <typename ValueArg,
class HeapHashSet
: public HashSet<ValueArg, HashArg, TraitsArg, HeapAllocator> {
IS_GARBAGE_COLLECTED_TYPE();
using Base = HashSet<ValueArg, HashArg, TraitsArg>;
static_assert(WTF::IsTraceable<ValueArg>::value,
"For hash sets without traceable elements, use HashSet<> "
"instead of HeapHashSet<>");
DISALLOW_NEW();
static void CheckType() {
static_assert(
IsAllowedInContainer<ValueArg>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(WTF::IsTraceable<ValueArg>::value,
"For hash sets without traceable elements, use HashSet<> "
"instead of HeapHashSet<>");
}
public:
static void* AllocateObject(size_t size, bool eagerly_sweep) {
......@@ -500,16 +523,7 @@ class HeapHashSet
size, eagerly_sweep);
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
void* operator new[](size_t size) = delete;
void operator delete[](void* p) = delete;
void* operator new(size_t size, NotNullTag null_tag, void* location) {
return Base::operator new(size, null_tag, location);
}
void* operator new(size_t size, void* location) {
return Base::operator new(size, location);
}
HeapHashSet() { CheckType(); }
};
template <typename ValueArg,
......@@ -518,10 +532,19 @@ template <typename ValueArg,
class HeapLinkedHashSet
: public LinkedHashSet<ValueArg, HashArg, TraitsArg, HeapAllocator> {
IS_GARBAGE_COLLECTED_TYPE();
using Base = LinkedHashSet<ValueArg, HashArg, TraitsArg>;
static_assert(WTF::IsTraceable<ValueArg>::value,
"For sets without traceable elements, use LinkedHashSet<> "
"instead of HeapLinkedHashSet<>");
DISALLOW_NEW();
// HeapLinkedHashSet is using custom callbacks for compaction that rely on the
// fact that the container itself does not move.
DISALLOW_IN_CONTAINER();
static void CheckType() {
static_assert(
IsAllowedInContainer<ValueArg>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(WTF::IsTraceable<ValueArg>::value,
"For sets without traceable elements, use LinkedHashSet<> "
"instead of HeapLinkedHashSet<>");
}
public:
static void* AllocateObject(size_t size, bool eagerly_sweep) {
......@@ -529,16 +552,7 @@ class HeapLinkedHashSet
HeapLinkedHashSet<ValueArg, HashArg, TraitsArg>>(size, eagerly_sweep);
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
void* operator new[](size_t size) = delete;
void operator delete[](void* p) = delete;
void* operator new(size_t size, NotNullTag null_tag, void* location) {
return Base::operator new(size, null_tag, location);
}
void* operator new(size_t size, void* location) {
return Base::operator new(size, location);
}
HeapLinkedHashSet() { CheckType(); }
};
template <typename ValueArg,
......@@ -552,10 +566,16 @@ class HeapListHashSet
HashArg,
HeapListHashSetAllocator<ValueArg, inlineCapacity>> {
IS_GARBAGE_COLLECTED_TYPE();
using Base = ListHashSet<ValueArg, inlineCapacity, HashArg>;
static_assert(WTF::IsTraceable<ValueArg>::value,
"For sets without traceable elements, use ListHashSet<> "
"instead of HeapListHashSet<>");
DISALLOW_NEW();
static void CheckType() {
static_assert(
IsAllowedInContainer<ValueArg>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(WTF::IsTraceable<ValueArg>::value,
"For sets without traceable elements, use ListHashSet<> "
"instead of HeapListHashSet<>");
}
public:
static void* AllocateObject(size_t size, bool eagerly_sweep) {
......@@ -564,16 +584,7 @@ class HeapListHashSet
eagerly_sweep);
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
void* operator new[](size_t size) = delete;
void operator delete[](void* p) = delete;
void* operator new(size_t size, NotNullTag null_tag, void* location) {
return Base::operator new(size, null_tag, location);
}
void* operator new(size_t size, void* location) {
return Base::operator new(size, location);
}
HeapListHashSet() { CheckType(); }
};
template <typename Value,
......@@ -582,23 +593,41 @@ template <typename Value,
class HeapHashCountedSet
: public HashCountedSet<Value, HashFunctions, Traits, HeapAllocator> {
IS_GARBAGE_COLLECTED_TYPE();
static_assert(WTF::IsTraceable<Value>::value,
"For counted sets without traceable elements, use "
"HashCountedSet<> instead of HeapHashCountedSet<>");
DISALLOW_NEW();
static void CheckType() {
static_assert(
IsAllowedInContainer<Value>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(WTF::IsTraceable<Value>::value,
"For counted sets without traceable elements, use "
"HashCountedSet<> instead of HeapHashCountedSet<>");
}
public:
static void* AllocateObject(size_t size, bool eagerly_sweep) {
return ThreadHeap::Allocate<
HeapHashCountedSet<Value, HashFunctions, Traits>>(size, eagerly_sweep);
}
HeapHashCountedSet() { CheckType(); }
};
template <typename T, wtf_size_t inlineCapacity = 0>
class HeapVector : public Vector<T, inlineCapacity, HeapAllocator> {
IS_GARBAGE_COLLECTED_TYPE();
using Base = Vector<T, inlineCapacity, HeapAllocator>;
DISALLOW_NEW();
public:
HeapVector() {
static void CheckType() {
static_assert(
IsAllowedInContainer<T>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(WTF::IsTraceable<T>::value,
"For vectors without traceable elements, use Vector<> "
"instead of HeapVector<>");
}
public:
static void* AllocateObject(size_t size, bool eagerly_sweep) {
// On-heap HeapVectors generally should not have inline capacity, but it is
// hard to avoid when using a type alias. Hence we only disallow the
......@@ -609,43 +638,45 @@ class HeapVector : public Vector<T, inlineCapacity, HeapAllocator> {
eagerly_sweep);
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
void* operator new[](size_t size) = delete;
void operator delete[](void* p) = delete;
void* operator new(size_t size, NotNullTag null_tag, void* location) {
return Base::operator new(size, null_tag, location);
}
void* operator new(size_t size, void* location) {
return Base::operator new(size, location);
}
HeapVector() { CheckType(); }
explicit HeapVector(wtf_size_t size)
: Vector<T, inlineCapacity, HeapAllocator>(size) {}
: Vector<T, inlineCapacity, HeapAllocator>(size) {
CheckType();
}
HeapVector(wtf_size_t size, const T& val)
: Vector<T, inlineCapacity, HeapAllocator>(size, val) {}
: Vector<T, inlineCapacity, HeapAllocator>(size, val) {
CheckType();
}
template <wtf_size_t otherCapacity>
HeapVector(const HeapVector<T, otherCapacity>& other)
: Vector<T, inlineCapacity, HeapAllocator>(other) {}
: Vector<T, inlineCapacity, HeapAllocator>(other) {
CheckType();
}
HeapVector(std::initializer_list<T> elements)
: Vector<T, inlineCapacity, HeapAllocator>(elements) {}
: Vector<T, inlineCapacity, HeapAllocator>(elements) {
CheckType();
}
};
template <typename T, wtf_size_t inlineCapacity = 0>
class HeapDeque : public Deque<T, inlineCapacity, HeapAllocator> {
IS_GARBAGE_COLLECTED_TYPE();
using Base = Deque<T, inlineCapacity, HeapAllocator>;
DISALLOW_NEW();
public:
HeapDeque() {
static void CheckType() {
static_assert(
IsAllowedInContainer<T>::value,
"Not allowed to directly nest type. Use Member<> indirection instead.");
static_assert(WTF::IsTraceable<T>::value,
"For vectors without traceable elements, use Deque<> instead "
"of HeapDeque<>");
}
public:
static void* AllocateObject(size_t size, bool eagerly_sweep) {
// On-heap HeapDeques generally should not have inline capacity, but it is
// hard to avoid when using a type alias. Hence we only disallow the
......@@ -656,22 +687,17 @@ class HeapDeque : public Deque<T, inlineCapacity, HeapAllocator> {
eagerly_sweep);
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
void* operator new[](size_t size) = delete;
void operator delete[](void* p) = delete;
void* operator new(size_t size, NotNullTag null_tag, void* location) {
return Base::operator new(size, null_tag, location);
}
void* operator new(size_t size, void* location) {
return Base::operator new(size, location);
}
HeapDeque() { CheckType(); }
explicit HeapDeque(wtf_size_t size)
: Deque<T, inlineCapacity, HeapAllocator>(size) {}
: Deque<T, inlineCapacity, HeapAllocator>(size) {
CheckType();
}
HeapDeque(wtf_size_t size, const T& val)
: Deque<T, inlineCapacity, HeapAllocator>(size, val) {}
: Deque<T, inlineCapacity, HeapAllocator>(size, val) {
CheckType();
}
HeapDeque& operator=(const HeapDeque& other) {
HeapDeque<T> copy(other);
......@@ -684,23 +710,6 @@ class HeapDeque : public Deque<T, inlineCapacity, HeapAllocator> {
: Deque<T, inlineCapacity, HeapAllocator>(other) {}
};
template <typename T>
class HeapDoublyLinkedList : public DoublyLinkedList<T, Member<T>> {
IS_GARBAGE_COLLECTED_TYPE();
DISALLOW_NEW();
public:
HeapDoublyLinkedList() {
static_assert(WTF::IsGarbageCollectedType<T>::value,
"This should only be used for garbage collected types.");
}
void Trace(Visitor* visitor) {
visitor->Trace(this->head_);
visitor->Trace(this->tail_);
}
};
} // namespace blink
namespace WTF {
......
......@@ -2304,9 +2304,9 @@ TEST(HeapTest, LargeVector) {
// Try to allocate a HeapVectors larger than kMaxHeapObjectSize
// (crbug.com/597953).
wtf_size_t size = kMaxHeapObjectSize / sizeof(int);
Persistent<HeapVector<int>> vector =
MakeGarbageCollected<HeapVector<int>>(size);
const wtf_size_t size = kMaxHeapObjectSize / sizeof(Member<IntWrapper>);
Persistent<HeapVector<Member<IntWrapper>>> vector =
MakeGarbageCollected<HeapVector<Member<IntWrapper>>>(size);
EXPECT_LE(size, vector->capacity());
}
......@@ -2529,7 +2529,7 @@ TEST(HeapTest, HeapCollectionTypes) {
MakeGarbageCollected<PrimitiveMember>();
Persistent<MemberSet> set = MakeGarbageCollected<MemberSet>();
Persistent<MemberSet> set2 = MakeGarbageCollected<MemberSet>();
Persistent<MemberCountedSet> set3 = new MemberCountedSet();
Persistent<MemberCountedSet> set3 = MakeGarbageCollected<MemberCountedSet>();
Persistent<MemberVector> vector = MakeGarbageCollected<MemberVector>();
Persistent<MemberVector> vector2 = MakeGarbageCollected<MemberVector>();
Persistent<VectorWU> vector_wu = MakeGarbageCollected<VectorWU>();
......@@ -3107,7 +3107,8 @@ TEST(HeapTest, HeapWeakCollectionSimple) {
Persistent<StrongWeak> strong_weak = MakeGarbageCollected<StrongWeak>();
Persistent<WeakWeak> weak_weak = MakeGarbageCollected<WeakWeak>();
Persistent<WeakSet> weak_set = MakeGarbageCollected<WeakSet>();
Persistent<WeakCountedSet> weak_counted_set = new WeakCountedSet();
Persistent<WeakCountedSet> weak_counted_set =
MakeGarbageCollected<WeakCountedSet>();
Persistent<IntWrapper> two = MakeGarbageCollected<IntWrapper>(2);
......@@ -5982,58 +5983,6 @@ TEST(HeapTest, HeapHashMapCallsDestructor) {
EXPECT_TRUE(string.Impl()->HasOneRef());
}
class DoublyLinkedListNodeImpl
: public GarbageCollectedFinalized<DoublyLinkedListNodeImpl>,
public DoublyLinkedListNode<DoublyLinkedListNodeImpl> {
public:
DoublyLinkedListNodeImpl() = default;
static int destructor_calls_;
~DoublyLinkedListNodeImpl() { ++destructor_calls_; }
void Trace(Visitor* visitor) {
visitor->Trace(prev_);
visitor->Trace(next_);
}
private:
friend class WTF::DoublyLinkedListNode<DoublyLinkedListNodeImpl>;
Member<DoublyLinkedListNodeImpl> prev_;
Member<DoublyLinkedListNodeImpl> next_;
};
int DoublyLinkedListNodeImpl::destructor_calls_ = 0;
template <typename T>
class HeapDoublyLinkedListContainer
: public GarbageCollected<HeapDoublyLinkedListContainer<T>> {
public:
HeapDoublyLinkedListContainer<T>() = default;
HeapDoublyLinkedList<T> list_;
void Trace(Visitor* visitor) { visitor->Trace(list_); }
};
TEST(HeapTest, HeapDoublyLinkedList) {
Persistent<HeapDoublyLinkedListContainer<DoublyLinkedListNodeImpl>>
container = MakeGarbageCollected<
HeapDoublyLinkedListContainer<DoublyLinkedListNodeImpl>>();
DoublyLinkedListNodeImpl::destructor_calls_ = 0;
container->list_.Append(MakeGarbageCollected<DoublyLinkedListNodeImpl>());
container->list_.Append(MakeGarbageCollected<DoublyLinkedListNodeImpl>());
PreciselyCollectGarbage();
EXPECT_EQ(DoublyLinkedListNodeImpl::destructor_calls_, 0);
container->list_.RemoveHead();
PreciselyCollectGarbage();
EXPECT_EQ(DoublyLinkedListNodeImpl::destructor_calls_, 1);
container->list_.RemoveHead();
PreciselyCollectGarbage();
EXPECT_EQ(DoublyLinkedListNodeImpl::destructor_calls_, 2);
}
TEST(HeapTest, PromptlyFreeStackAllocatedHeapVector) {
NormalPageArena* normal_arena;
Address before;
......@@ -6151,6 +6100,9 @@ TEST(HeapTest, GarbageCollectedMixinIsAliveDuringConstruction) {
using O = ObjectWithMixinWithCallbackBeforeInitializer<IntWrapper>;
MakeGarbageCollected<O>(base::BindOnce(
[](O::Mixin* thiz) { CHECK(ThreadHeap::IsHeapObjectAlive(thiz)); }));
using P = HeapVector<Member<HeapLinkedHashSet<Member<IntWrapper>>>>;
MakeGarbageCollected<P>();
}
} // namespace blink
......@@ -658,57 +658,6 @@ TEST(IncrementalMarkingTest, HeapVectorEagerTracingStopsAtMember) {
}
}
// =============================================================================
// HeapDoublyLinkedList support. ===============================================
// =============================================================================
namespace {
class ObjectNode : public GarbageCollected<ObjectNode>,
public DoublyLinkedListNode<ObjectNode> {
public:
explicit ObjectNode(Object* obj) : obj_(obj) {}
void Trace(Visitor* visitor) {
visitor->Trace(obj_);
visitor->Trace(prev_);
visitor->Trace(next_);
}
private:
friend class WTF::DoublyLinkedListNode<ObjectNode>;
Member<Object> obj_;
Member<ObjectNode> prev_;
Member<ObjectNode> next_;
};
} // namespace
TEST(IncrementalMarkingTest, HeapDoublyLinkedListPush) {
auto* obj = MakeGarbageCollected<Object>();
ObjectNode* obj_node = MakeGarbageCollected<ObjectNode>(obj);
HeapDoublyLinkedList<ObjectNode> list;
{
ExpectWriteBarrierFires scope(ThreadState::Current(), {obj_node});
list.Push(obj_node);
// |obj| will be marked once |obj_node| gets processed.
EXPECT_FALSE(obj->IsMarked());
}
}
TEST(IncrementalMarkingTest, HeapDoublyLinkedListAppend) {
auto* obj = MakeGarbageCollected<Object>();
ObjectNode* obj_node = MakeGarbageCollected<ObjectNode>(obj);
HeapDoublyLinkedList<ObjectNode> list;
{
ExpectWriteBarrierFires scope(ThreadState::Current(), {obj_node});
list.Append(obj_node);
// |obj| will be marked once |obj_node| gets processed.
EXPECT_FALSE(obj->IsMarked());
}
}
// =============================================================================
// HeapDeque support. ==========================================================
// =============================================================================
......@@ -1602,7 +1551,7 @@ TEST(IncrementalMarkingTest, DropBackingStore) {
// Regression test: https://crbug.com/828537
using WeakStore = HeapHashCountedSet<WeakMember<Object>>;
Persistent<WeakStore> persistent(new WeakStore);
Persistent<WeakStore> persistent(MakeGarbageCollected<WeakStore>());
persistent->insert(MakeGarbageCollected<Object>());
IncrementalMarkingTestDriver driver(ThreadState::Current());
driver.Start();
......@@ -1619,7 +1568,7 @@ TEST(IncrementalMarkingTest, WeakCallbackDoesNotReviveDeletedValue) {
// std::pair avoids treating the hashset backing as weak backing.
using WeakStore = HeapHashCountedSet<std::pair<WeakMember<Object>, size_t>>;
Persistent<WeakStore> persistent(new WeakStore);
Persistent<WeakStore> persistent(MakeGarbageCollected<WeakStore>());
// Create at least two entries to avoid completely emptying out the data
// structure. The values for .second are chosen to be non-null as they
// would otherwise count as empty and be skipped during iteration after the
......@@ -1654,7 +1603,7 @@ TEST(IncrementalMarkingTest, NoBackingFreeDuringIncrementalMarking) {
// Only reproduces in ASAN configurations.
using WeakStore = HeapHashCountedSet<std::pair<WeakMember<Object>, size_t>>;
Persistent<WeakStore> persistent(new WeakStore);
Persistent<WeakStore> persistent(MakeGarbageCollected<WeakStore>());
// Prefill the collection to grow backing store. A new backing store allocaton
// would trigger the write barrier, mitigating the bug where a backing store
// is promptly freed.
......@@ -1674,7 +1623,7 @@ TEST(IncrementalMarkingTest, NoBackingFreeDuringIncrementalMarking) {
TEST(IncrementalMarkingTest, DropReferenceWithHeapCompaction) {
using Store = HeapHashCountedSet<Member<Object>>;
Persistent<Store> persistent(new Store());
Persistent<Store> persistent(MakeGarbageCollected<Store>());
persistent->insert(MakeGarbageCollected<Object>());
IncrementalMarkingTestDriver driver(ThreadState::Current());
ThreadState::Current()->EnableCompactionForNextGCForTesting();
......@@ -1711,7 +1660,7 @@ TEST(IncrementalMarkingTest, HasInlineCapacityCollectionWithHeapCompaction) {
TEST(IncrementalMarkingTest, WeakHashMapHeapCompaction) {
using Store = HeapHashCountedSet<WeakMember<Object>>;
Persistent<Store> persistent(new Store());
Persistent<Store> persistent(MakeGarbageCollected<Store>());
IncrementalMarkingTestDriver driver(ThreadState::Current());
ThreadState::Current()->EnableCompactionForNextGCForTesting();
......
......@@ -128,11 +128,9 @@ class DoublyLinkedList {
template <typename T, typename PointerType>
inline DoublyLinkedList<T, PointerType>::DoublyLinkedList()
: head_(nullptr), tail_(nullptr) {
static_assert(
!IsGarbageCollectedType<T>::value ||
!std::is_same<PointerType, T*>::value,
"Cannot use DoublyLinkedList<> with garbage collected types, use "
"HeapDoublyLinkedList<> instead.");
static_assert(!IsGarbageCollectedType<T>::value ||
!std::is_same<PointerType, T*>::value,
"Cannot use DoublyLinkedList<> with garbage collected types.");
}
template <typename T, typename PointerType>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment