12

C++11 §12.1/14:

During the construction of a const object, if the value of the object or any of its subobjects is accessed through an lvalue that is not obtained, directly or indirectly, from the constructor’s this pointer, the value of the object or subobject thus obtained is unspecified. [Example:

struct C; void no_opt(C*); struct C { int c; C() : c(0) { no_opt(this); } }; const C cobj; void no_opt(C* cptr) { // value of cobj.c is unspecified int i = cobj.c * 100; cptr->c = 1; // value of cobj.c is unspecified cout << cobj.c * 100 << '\n'; } 

Compiling the above example outputs 100. My question is why is the value of cobj.c should be unspecified when the initialization list sets it to 0 before entering constructor? How is this behavior different in case if a non-const object is used?

5
  • I would note that in the example above cobj.c is accessed before cobj is fully constructed. It seems to me that even for non-const objects this action is dubious. Commented Jan 5, 2012 at 7:54
  • @MatthieuM. Why? Looks valid to me (even if you add a base class to struct C). Commented Jan 5, 2012 at 8:04
  • @VJovic: As long as the constructor has not run, the object is not alive yet -- see Sutter's take in Constructor Failures (or, The Objects That Never Were). If an object is not alive yet, accessing it is dubious. I don't say it is necessarily undefined or unspecified, just that it "smells bad". Commented Jan 5, 2012 at 8:12
  • 1
    I don't understand the wording. cptr is obtained from this, but the quote says "not obtained" (how else)? Also, this quote means constructor body shall not modify any members, or else behavior will be unspecified for const objects? Commented Jan 5, 2012 at 10:35
  • 2
    @visitor: The problem is using cobj before the constructor returns. The read access is the offending one. Commented Jan 5, 2012 at 11:29

2 Answers 2

6

Genuinely const objects may be treated by the compiler as legitimate constants. It can assume their values never change or even store them in const memory, e.g. ROM or Flash. So, you need to use the non-const access path provided by this as long as the object is, in fact, not constant. This condition only exists during object construction and destruction.

Offhand, I think there does not need to be a corresponding requirement for destructors because the object lifetime has already ended and cobj.c is inaccessible as soon as the destructor for cobj begins.

As Matthieu mentions, it is a strong "code smell" to be accessing an object besides through this during construction or destruction. Reviewing C++11 §3.8 [basic.life] ¶1 and 6, it would appear that cobj.c inside the constructor is UB for the same reason it is inside the destructor, regardless of the object being const or §12.1/14, because its lifetime does not begin until initialization is complete (the constructor returns).

It might be likely to work, but it will ring alarms for good C++ programmers, and by the book it is illegal.

Sign up to request clarification or add additional context in comments.

4 Comments

C() : c(0) { c=1; } - so what you are saying is that this is also not valid?
This seems off-topic: it does not address the fact that cobj.c has an unspecified value during the run of C's constructor (even after the initialization's list ran). It also does not address why the behavior would be different (in this regard) between a const and non-const objects.
@MatthieuM. I think I've covered both those issues. The problem is that the compiler can recognize the cobj.c access path as having a value that can't change. No matter what function accesses it, a const object which does not have a constant value is off limits.
Ah, I finally got what you were saying with "non-const access path", sorry for being dense.
3

The reason for the quoted rule is to allow the compiler to make optimizations based on the const-ness of the object. For example, depending on optimization, your compiler might replace the second cobj.c * 100 in no_opt with i. More likely, in this particular case, the optimizer will suppress the i and its initialization completely, so the code will appear to work. But this might not be the case if you also output i, before changing cptr->c; it all depends on how agressive the compiler optimizes. But the compiler is allowed to assume that *cptr is not an alias for cobj, because cobj is a const object, where as you modify through *cptr, so it cannot point to a const object without undefined behavior.

If the object isn't const, of course, the issue doesn't occur; the compiler must always take into account a possible aliasing between *cptr and cobj.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.