Skip to content

Commit 89ce203

Browse files
committed
Merge pull request godotengine#106997 from Ivorforce/fixed-move-semantics
Fix `FixedVector` move and copy semantics.
2 parents 99ab454 + c3476b8 commit 89ce203

File tree

2 files changed

+79
-12
lines changed

2 files changed

+79
-12
lines changed

core/templates/fixed_vector.h

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,15 @@ class FixedVector {
5353

5454
public:
5555
_FORCE_INLINE_ constexpr FixedVector() = default;
56+
5657
constexpr FixedVector(std::initializer_list<T> p_init) {
5758
ERR_FAIL_COND(p_init.size() > CAPACITY);
5859
for (const T &element : p_init) {
5960
memnew_placement(ptr() + _size++, T(element));
6061
}
6162
}
6263

63-
template <uint32_t p_capacity>
64-
constexpr FixedVector(const FixedVector<T, p_capacity> &p_from) {
65-
ERR_FAIL_COND(p_from.size() > CAPACITY);
64+
constexpr FixedVector(const FixedVector &p_from) {
6665
if constexpr (std::is_trivially_copyable_v<T>) {
6766
// Copy size and all provided elements at once.
6867
memcpy((void *)&_size, (void *)&p_from._size, sizeof(_size) + DATA_PADDING + p_from.size() * sizeof(T));
@@ -73,15 +72,48 @@ class FixedVector {
7372
}
7473
}
7574

76-
template <uint32_t p_capacity>
77-
constexpr FixedVector(FixedVector<T, p_capacity> &&p_from) {
78-
ERR_FAIL_COND(p_from.size() > CAPACITY);
75+
constexpr FixedVector(FixedVector &&p_from) {
7976
// Copy size and all provided elements at once.
8077
// Note: Assumes trivial relocatability.
8178
memcpy((void *)&_size, (void *)&p_from._size, sizeof(_size) + DATA_PADDING + p_from.size() * sizeof(T));
8279
p_from._size = 0;
8380
}
8481

82+
constexpr FixedVector &operator=(const FixedVector &p_from) {
83+
if constexpr (std::is_trivially_copyable_v<T>) {
84+
// Copy size and all provided elements at once.
85+
memcpy((void *)&_size, (void *)&p_from._size, sizeof(_size) + DATA_PADDING + p_from.size() * sizeof(T));
86+
} else {
87+
// Destruct extraneous elements.
88+
if constexpr (!std::is_trivially_destructible_v<T>) {
89+
for (uint32_t i = p_from.size(); i < _size; i++) {
90+
ptr()[i].~T();
91+
}
92+
}
93+
94+
_size = 0; // Loop-assign the rest.
95+
for (const T &element : p_from) {
96+
ptr()[_size++] = element;
97+
}
98+
}
99+
return *this;
100+
}
101+
102+
constexpr FixedVector &operator=(FixedVector &&p_from) {
103+
// Destruct extraneous elements.
104+
if constexpr (!std::is_trivially_destructible_v<T>) {
105+
for (uint32_t i = p_from.size(); i < _size; i++) {
106+
ptr()[i].~T();
107+
}
108+
}
109+
110+
// Relocate elements (and size) into our buffer.
111+
memcpy((void *)&_size, (void *)&p_from._size, sizeof(_size) + DATA_PADDING + p_from.size() * sizeof(T));
112+
p_from._size = 0;
113+
114+
return *this;
115+
}
116+
85117
~FixedVector() {
86118
if constexpr (!std::is_trivially_destructible_v<T>) {
87119
for (uint32_t i = 0; i < _size; i++) {
@@ -138,6 +170,12 @@ class FixedVector {
138170
_size++;
139171
}
140172

173+
constexpr void push_back(T &&p_val) {
174+
ERR_FAIL_COND(_size >= CAPACITY);
175+
memnew_placement(ptr() + _size, T(std::move(p_val)));
176+
_size++;
177+
}
178+
141179
constexpr void pop_back() {
142180
ERR_FAIL_COND(_size == 0);
143181
_size--;

tests/core/templates/test_fixed_vector.h

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@
3636

3737
namespace TestFixedVector {
3838

39+
struct MoveOnly {
40+
bool is_alive = true;
41+
42+
MoveOnly() = default;
43+
MoveOnly(const MoveOnly &p) = delete;
44+
MoveOnly &operator=(const MoveOnly &p) = delete;
45+
46+
MoveOnly(MoveOnly &&p) {
47+
is_alive = p.is_alive;
48+
p.is_alive = false;
49+
}
50+
MoveOnly &operator=(MoveOnly &&p) {
51+
if (&p == this) {
52+
return *this;
53+
}
54+
is_alive = p.is_alive;
55+
p.is_alive = false;
56+
return *this;
57+
}
58+
};
59+
60+
static_assert(!std::is_trivially_copyable_v<MoveOnly>);
61+
static_assert(!std::is_trivially_constructible_v<MoveOnly>);
62+
3963
TEST_CASE("[FixedVector] Basic Checks") {
4064
FixedVector<uint16_t, 1> vector;
4165
CHECK_EQ(vector.capacity(), 1);
@@ -62,12 +86,6 @@ TEST_CASE("[FixedVector] Basic Checks") {
6286
CHECK_EQ(vector1[0], 1);
6387
CHECK_EQ(vector1[1], 2);
6488

65-
FixedVector<uint16_t, 3> vector2(vector1);
66-
CHECK_EQ(vector2.capacity(), 3);
67-
CHECK_EQ(vector2.size(), 2);
68-
CHECK_EQ(vector2[0], 1);
69-
CHECK_EQ(vector2[1], 2);
70-
7189
FixedVector<Variant, 3> vector_variant;
7290
CHECK_EQ(vector_variant.size(), 0);
7391
CHECK_EQ(vector_variant.capacity(), 3);
@@ -79,6 +97,17 @@ TEST_CASE("[FixedVector] Basic Checks") {
7997
CHECK_EQ(vector_variant[0], "Test");
8098
CHECK_EQ(vector_variant[1], Variant(1));
8199
CHECK_EQ(vector_variant[2].get_type(), Variant::NIL);
100+
101+
// Test that move-only types are transferred.
102+
FixedVector<MoveOnly, 1> a;
103+
a.push_back(MoveOnly());
104+
CHECK_EQ(a.size(), 1);
105+
FixedVector<MoveOnly, 1> b(std::move(a));
106+
CHECK_EQ(a.size(), 0);
107+
CHECK_EQ(b.size(), 1);
108+
a = std::move(b);
109+
CHECK_EQ(a.size(), 1);
110+
CHECK_EQ(b.size(), 0);
82111
}
83112

84113
TEST_CASE("[FixedVector] Alignment Checks") {

0 commit comments

Comments
 (0)