2224. Ambiguous status of access to non-live objects

Section: 20.5.4.10 [res.on.objects] Status: C++17 Submitter: Geoffrey Romer Opened: 2012-12-17 Last modified: 2017-09-10

Priority: 2

View all other issues in [res.on.objects].

View all issues with C++17 status.

Discussion:

The standard currently does not discuss when library objects may be accessed, except in a non-normative note pertaining to synchronization in [res.on.objects], leaving it ambiguous whether single-threaded code can access a library object during its construction or destruction. For example, there is a reasonable question as to what happens if the deleter supplied to a unique_ptr transitively accesses the unique_ptr itself during unique_ptr's destruction; a straightforward reading suggests that this is permitted, and that the deleter will see the unique_ptr still holding the originally stored pointer, but consensus on the LWG reflector indicates this was not the intent (see discussion beginning with c++std-lib-33362).

[2013-03-15 Issues Teleconference]

Moved to Open.

Geoffrey will provide an example that clearly highlights the issue.

[2013-03-19 Geoffrey provides revised resolution and an example]

I contend that the most straightforward reading of the current standard requires the following example code to print "good" (because ~unique_ptr is not specified to modify the state of the internal pointer), but the consensus on the reflector was that its behavior should be undefined.

This example also shows that, contrary to a comment in the telecon, the PR is not tautological. 15.7 [class.cdtor]/p4 explicitly permits member function calls during destruction, so the behavior of this code is well-defined as far as the core language is concerned, despite the fact that it accesses a library object after the end of the object's lifetime. If we want this code to have undefined behavior, we need to specify that at the library level.

#include <memory>
#include <iostream>

class A;

struct B {
 std::unique_ptr<A> a;
};

struct A {
 B* b;
 ~A() {
   if (b->a.get() == this) {
     std::cout << "good" << std::endl;
   }
 }
};

int main() {
 B b;
 b.a.reset(new A);
 b.a->b = &b;
}

Previous resolution:

  1. Change the title of sub-clause 20.5.4.10 [res.on.objects] as indicated:

    Shared objects and the libraryLibrary object access [res.on.objects]

  2. Edit 20.5.4.10 [res.on.objects] p2 as indicated:

    -2- [Note: In particular, the program is required to ensure that completion of the constructor of any object of a class type defined in the standard library happens before any other member function invocation on that object and, unless otherwise specified, to ensure that completion of any member function invocation other than destruction on such an object happens before destruction of that object. This applies even to objects such as mutexes intended for thread synchronization. — end note] If an object of a standard library type is accessed outside of the object's lifetime (6.6.3 [basic.life]), the behavior is undefined unless otherwise specified.

[2014 Urbana]

STL: is this resolved by our change to the reeentrancy rules? [LWG 2414]
GR: don't think that solves the multi-threaded case
MC: I like changing the note to normative text
GR: uses the magic "happens before" words, and "access" is magic too
JW: I like this. strict improvement, uses the right wording we have to say this properly
STL: I like the last sentence of the note, could we add that as a new note at the end?
So add "[Note: This applies even to objects such as mutexes intended for thread synchronization.]" to the end and move to Ready

Proposed resolution:

This wording is relative to N3485.

  1. Change the title of sub-clause 20.5.4.10 [res.on.objects] as indicated:

    Shared objects and the libraryLibrary object access [res.on.objects]

  2. Edit 20.5.4.10 [res.on.objects] p2 as indicated: [Editorial remark: The motivation, is to be more precise about the meaning of "outside the object's lifetime" in the presence of threads — end editorial remark]

    -2- [Note: In particular, the program is required to ensure that completion of the constructor of any object of a class type defined in the standard library happens before any other member function invocation on that object and, unless otherwise specified, to ensure that completion of any member function invocation other than destruction on such an object happens before destruction of that object. This applies even to objects such as mutexes intended for thread synchronization. — end note] If an object of a standard library type is accessed, and the beginning of the object's lifetime (6.6.3 [basic.life]) does not happen before the access, or the access does not happen before the end of the object's lifetime, the behavior is undefined unless otherwise specified. [Note: This applies even to objects such as mutexes intended for thread synchronization. — end note]