Thread A sets a shared reference to a newly allocated and null initialized reference to Foo:
shared = new Foo();
While that's running, thread B invokes a method on the reference that assumes bar is non-null: shared.useBar(); // null pointer exception
Later, thread A runs the constructor for Foo.For example, this is code that any C or C++ compiler will happily run and do something:
struct Bar {
int b;
};
struct Foo {
struct Bar bar;
} foo;
strcpy((char*)(&foo), "ABC");
Or in relation to null C++ references: int& foo(int* p) {
return *p;
}
int &r = foo(nullptr); //UB, but in practice will likely result in a null reference at runtime
Similarly, accessing an object from multiple threads without synchronization means its value is not fully defined in Java. Unlike C or C++, it is at least known to be a Java type, not a memory corruption vulnerability.In your example, it's the dereference of the pointer to p that is undefined behavior, so anything that happens after that point is also undefined behavior. Note that means there is never an actual assignment to r if p is null.
As I mentioned earlier, this might seem like quibbling with definitions, but this is the proper mental model to have with respect to C++'s semantics.
Having said that, I don't disagree with the main crux of your point, which is that C++'s semantics are terrible and there is little that the language provides to write correct code, but I do think there are subtleties on this matter that are worth clarifying.
My point is that we can compare two things: valid programs, or programs that compile.
In valid C++ programs, references can't be null and there are no data races. In valid Java programs, all final fields initialized in an object's constructor have that value for the lifetime of the object.
If we compare invalid programs that compile, which is an important point as well, then those guarantees go out the window. But here Java is much more forgiving than C++: if you have improper synchronization, you may see fields which are null instead of having their final value, which is bad and confusing. But in C++ with improper synchronization, you can see literally any outcome at all.
https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.ht...
>An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
If you don't publish a reference to the object from within the constructor, you will not see a null value of the final field, even if the object itself was unsafely published across threads via a non-volatile field.
https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename...