RFR: 8354954: Typed static memory for late initialization of static class members in Hotspot [v4]
Kim Barrett
kbarrett at openjdk.org
Mon Apr 21 21:59:48 UTC 2025
On Mon, 21 Apr 2025 19:59:21 GMT, Kim Barrett <kbarrett at openjdk.org> wrote:
>> This would induce an additional indirection on every access, though.
>
> That additional indirection is what we already have with the current idiom. I'm looking for a way to be
> safe while removing the "do we need to do laundry" question. But I'm currently thinking we don't need
> to do laundry, though I'm still working through the details of the rationale, and still might end up deciding
> I'm wrong.
I think something this works, and that no laundering is required.
template<typename T>
class StableValue {
union { T _obj; };
DEBUG_ONLY(bool _initialized;)
public:
StableValue() DEBUG_ONLY(: _initialized(false)) {}
~StableValue() {}
NONCOPYABLE(StableValue);
T* ptr() {
assert(_initialized, "uninitialized");
return &_obj;
}
T* operator->() { return ptr(); }
T& operator*() { return *ptr(); }
template<typename... Args>
void initialize(Args&&... args) {
assert(!_initialized, "already initialized");
using NCVP = std::add_pointer_t<std::remove_cv_t<T>>;
::new (const_cast<NCVP>(_obj)) T(std::forward<Args>(args)...);
}
};
[Note that I've made a few other improvements, such as supporting a cv-qualified
type and being non-copyable. Obviously there's lots of missing comments...]
The rationale for not needing laundering is as follows.
std::launder is introduced to work around some restrictions involving reuse of
storage for a different object. Specifically, C++17 6.8.8 (Object lifetime)
says
--- begin quote ---
If, after the lifetime of an object has ended and before the storage which the
object occupied is reused or released, a new object is created at the storage
location which the original object occupied, a pointer that pointed to the
original object, a reference that referred to the original object, or the name
of the original object will automatically refer to the new object and, once
the lifetime of the new object has started, can be used to manipulate the new
object, if:
[... list of requirements ...]
[Note: Note: If these conditions are not met, a pointer to the new object can
be obtained from a pointer that represents the address of its storage by
calling std::launder. -- end note.]
--- end quote ---
Other than it's definition and an example, that's the only occurrence of
"launder" in C++17.
But if we're careful, that section doesn't apply at all. By using the
uninitialized anonymous union trick (or a similarly uninitialized and
appropriately aligned and sized char array), we have storage but not an object
whose lifetime has ended. There wasn't a prior object in that storage.
Instead we're dealing with storage either before or after the object's
lifetime has started. There are restrictions on what one can do with a pointer
to storage before the lifetime of the object starts, but we're not violating
any of those. The object's lifetime starts with the placement-new.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2053041649
More information about the hotspot-dev
mailing list