RFR: 8354954: Typed static memory for late initialization of static class members in Hotspot [v4]

Quan Anh Mai qamai at openjdk.org
Tue Apr 22 04:55:48 UTC 2025


On Mon, 21 Apr 2025 21:56:58 GMT, Kim Barrett <kbarrett at openjdk.org> wrote:

>> 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.

I believe you are right, we do not need launder here. From [[class.union]/6](https://eel.is/c++draft/class.union#6):

> [Note [5](https://eel.is/c++draft/class.union#general-note-5): In cases where the above rule does not apply, the active member of a union can only be changed by the use of a placement [new-expression](https://eel.is/c++draft/expr.new#nt:new-expression)[.](https://eel.is/c++draft/class.union#general-6.sentence-1) — end note]
[Example [3](https://eel.is/c++draft/class.union#general-example-3): Consider an object u of a union type U having non-static data members m of type M and n of type N[.](https://eel.is/c++draft/class.union#general-6.sentence-2) If M has a non-trivial destructor and N has a non-trivial constructor (for instance, if they declare or inherit virtual functions), the active member of u can be safely switched from m to n using the destructor and placement [new-expression](https://eel.is/c++draft/expr.new#nt:new-expression) as follows:
u.m.~M();
new (&u.n) N;
— end example]

We can see that `::new(_&obj) T()` is considered an act to make `_obj` active, not to create a new `T`, the `_obj` itself has been created before at this location, it is just that it has not been initialized and its lifetime has not started. As a result, `_obj` can be used to refer to this object without launder.

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2053325889


More information about the hotspot-dev mailing list