Skip to content

Commit f90f036

Browse files
[libc] Move in_use into OptionalStorage (#73569)
The previous optional class would call the destructor on a non-trivially destructible object regardless of if it had already been reset. This patch fixes this by moving tracking for if the object exists into the internal storage class for optional.
1 parent 9a38a72 commit f90f036

File tree

2 files changed

+43
-24
lines changed

2 files changed

+43
-24
lines changed

libc/src/__support/CPP/optional.h

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ struct nullopt_t {
2525
LIBC_INLINE_VAR constexpr nullopt_t nullopt{};
2626

2727
// This is very simple implementation of the std::optional class. It makes
28-
// several assumptions that the underlying type is trivially constructable,
28+
// several assumptions that the underlying type is trivially constructible,
2929
// copyable, or movable.
3030
template <typename T> class optional {
3131
template <typename U, bool = !is_trivially_destructible<U>::value>
@@ -35,46 +35,63 @@ template <typename T> class optional {
3535
U stored_value;
3636
};
3737

38-
LIBC_INLINE ~OptionalStorage() { stored_value.~U(); }
38+
bool in_use = false;
39+
40+
LIBC_INLINE ~OptionalStorage() { reset(); }
41+
3942
LIBC_INLINE constexpr OptionalStorage() : empty() {}
4043

4144
template <typename... Args>
4245
LIBC_INLINE constexpr explicit OptionalStorage(in_place_t, Args &&...args)
4346
: stored_value(forward<Args>(args)...) {}
47+
48+
LIBC_INLINE constexpr void reset() {
49+
if (in_use)
50+
stored_value.~U();
51+
in_use = false;
52+
}
4453
};
4554

55+
// The only difference is that this type U doesn't have a nontrivial
56+
// destructor.
4657
template <typename U> struct OptionalStorage<U, false> {
4758
union {
4859
char empty;
4960
U stored_value;
5061
};
5162

52-
// The only difference is that this class doesn't have a destructor.
63+
bool in_use = false;
64+
5365
LIBC_INLINE constexpr OptionalStorage() : empty() {}
5466

5567
template <typename... Args>
5668
LIBC_INLINE constexpr explicit OptionalStorage(in_place_t, Args &&...args)
5769
: stored_value(forward<Args>(args)...) {}
70+
71+
LIBC_INLINE constexpr void reset() { in_use = false; }
5872
};
5973

6074
OptionalStorage<T> storage;
61-
bool in_use = false;
6275

6376
public:
6477
LIBC_INLINE constexpr optional() = default;
6578
LIBC_INLINE constexpr optional(nullopt_t) {}
6679

67-
LIBC_INLINE constexpr optional(const T &t)
68-
: storage(in_place, t), in_use(true) {}
80+
LIBC_INLINE constexpr optional(const T &t) : storage(in_place, t) {
81+
storage.in_use = true;
82+
}
6983
LIBC_INLINE constexpr optional(const optional &) = default;
7084

71-
LIBC_INLINE constexpr optional(T &&t)
72-
: storage(in_place, move(t)), in_use(true) {}
85+
LIBC_INLINE constexpr optional(T &&t) : storage(in_place, move(t)) {
86+
storage.in_use = true;
87+
}
7388
LIBC_INLINE constexpr optional(optional &&O) = default;
7489

7590
template <typename... ArgTypes>
7691
LIBC_INLINE constexpr optional(in_place_t, ArgTypes &&...Args)
77-
: storage(in_place, forward<ArgTypes>(Args)...), in_use(true) {}
92+
: storage(in_place, forward<ArgTypes>(Args)...) {
93+
storage.in_use = true;
94+
}
7895

7996
LIBC_INLINE constexpr optional &operator=(T &&t) {
8097
storage = move(t);
@@ -88,20 +105,18 @@ template <typename T> class optional {
88105
}
89106
LIBC_INLINE constexpr optional &operator=(const optional &) = default;
90107

91-
LIBC_INLINE constexpr void reset() {
92-
if (in_use)
93-
storage.~OptionalStorage();
94-
in_use = false;
95-
}
108+
LIBC_INLINE constexpr void reset() { storage.reset(); }
96109

97110
LIBC_INLINE constexpr const T &value() const & {
98111
return storage.stored_value;
99112
}
100113

101114
LIBC_INLINE constexpr T &value() & { return storage.stored_value; }
102115

103-
LIBC_INLINE constexpr explicit operator bool() const { return in_use; }
104-
LIBC_INLINE constexpr bool has_value() const { return in_use; }
116+
LIBC_INLINE constexpr explicit operator bool() const {
117+
return storage.in_use;
118+
}
119+
LIBC_INLINE constexpr bool has_value() const { return storage.in_use; }
105120
LIBC_INLINE constexpr const T *operator->() const {
106121
return &storage.stored_value;
107122
}

libc/test/src/__support/CPP/optional_test.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,18 @@ TEST(LlvmLibcOptionalTest, Tests) {
4242

4343
// For this test case, the destructor increments the pointed-to value.
4444
int holding = 1;
45-
optional<Contrived> Complicated(&holding);
46-
// Destructor was run once as part of copying the object.
47-
ASSERT_EQ(holding, 2);
48-
// Destructor was run a second time as part of destruction.
49-
Complicated.reset();
50-
ASSERT_EQ(holding, 3);
51-
// Destructor was not run a third time as the object is already destroyed.
52-
Complicated.reset();
45+
{
46+
optional<Contrived> Complicated(&holding);
47+
// Destructor was run once as part of copying the object.
48+
ASSERT_EQ(holding, 2);
49+
// Destructor was run a second time as part of destruction.
50+
Complicated.reset();
51+
ASSERT_EQ(holding, 3);
52+
// Destructor was not run a third time as the object is already destroyed.
53+
Complicated.reset();
54+
ASSERT_EQ(holding, 3);
55+
}
56+
// Make sure the destructor isn't called when the optional is destroyed.
5357
ASSERT_EQ(holding, 3);
5458

5559
// Test that assigning an optional to another works when set

0 commit comments

Comments
 (0)