15

So this answer made me think about the scenario where you assign the result of new to a pointer to a const. AFAIK, there's no reason you can't legally const_cast the constness away and actually modify the object in this situation:

struct X{int x;};

//....
const X* x = new X;
const_cast<X*>(x)->x = 0; // okay

But then I thought - what if you actually want new to create a const object. So I tried

struct X{};

//....
const X* x = new const X;

and it compiled!!!

Is this a GCC extension or is it standard behavior? I have never seen this in practice. If it's standard, I'll start using it whenever possible.

Community
  • 1
  • 1
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625

3 Answers3

17

new obviously doesn't create a const object (I hope).

If you ask new to create a const object, you get a const object.

there's no reason you can't legally const_cast the constness away and actually modify the object.

There is. The reason is that the language specification calls that out explicitly as undefined behaviour. So, in a way, you can, but that means pretty much nothing.

I don't know what you expected from this, but if you thought the issue was one of allocating in readonly memory or not, that's far from the point. That doesn't matter. A compiler can assume such an object can't change and optimise accordingly and you end up with unexpected results.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • +1 for pointing out that because you *can* do something doesn't mean that you *should* do it. – LihO Apr 01 '14 at 23:15
  • so `const X* x = new X` is a pointer to a const object? Even though `new X` doesn't create a new object ? – Luchian Grigore Apr 01 '14 at 23:16
  • Just to be clear, you *are* allowed to `delete` that const pointer right? Otherwise, how do you get rid of it? – Mysticial Apr 01 '14 at 23:16
  • @Luchian that's equivalent to `X* tmp = new X; const X* x = tmp;`, so yeah, that you can `const_cast` and mutate. – R. Martinho Fernandes Apr 01 '14 at 23:18
  • 1
    @Mysticial: I'd say that `delete` cleans up the memory regardless what occupies it. AFAIK the type is used just to determine the way the object should be cleaned (destructor) and to determine the size of the memory block that is about to be freed. – LihO Apr 01 '14 at 23:20
  • @Mysticial destructors can run normally on any const objects, and the deallocation functions (i.e. `operator delete`) don't deal with objects (only untyped pointers to raw memory), so there's no issue. – R. Martinho Fernandes Apr 01 '14 at 23:20
  • @Mysticial: Why wouldn't you? By extension, do you think that `const A a;` can never go out of scope? – Lightness Races in Orbit Apr 01 '14 at 23:20
  • 2
    @LuchianGrigore, if you create a non-const object and then assign it to a const object pointer, I believe you're able to const_cast it back to a non-const pointer and modify it. But I'm agreeing with this answer, you've created a const object and so it must remain. – Mark Ransom Apr 01 '14 at 23:20
  • @MarkRansom my initial statement was related to the answer I linked, which was about creating a non-const object, that's what I was referring to when I talked about const_cast. – Luchian Grigore Apr 01 '14 at 23:22
  • @LuchianGrigore: I have commented on the answer and downvoted accordingly. Unfortunately, it was a mis-answer by our esteemed colleague Vlad. – Lightness Races in Orbit Apr 01 '14 at 23:24
  • Whether you can _legally_ do it depends on your definition of legally. If it means: it compiles, because that's what `const_cast` is for - then yes, it is legal. If it means: does it comply with the standard, the answer is no. In any case, I think that programmers should file a document justifying each use of `const_cast` and should be judged by a special committee for this. If you declare something as `const`, it's telling the compiler you will treat it as such; IMO `const_cast` is like saying "Oops, I made up my mind, please forget what I told you earlier". – CompuChip Apr 02 '14 at 06:55
  • @CompuChip No, that's flat-out wrong. `const_cast` is not like saying that: just read my last paragraph. It's not like saying that because the compiler won't forget what you told it earlier (e.g. http://coliru.stacked-crooked.com/a/5ad38eabb96ea451). Please stop abusing `const_cast`. – R. Martinho Fernandes Apr 02 '14 at 08:50
11

const is part of the type. It doesn't matter whether you allocate your object with dynamic, static or automatic storage duration. It's still const. Casting away that constness and mutating the object would still be an undefined operation.

constness is an abstraction that the type system gives us to implement safety around non-mutable objects; it does so in large part to aid us in interaction with read-only memory, but that does not mean that its semantics are restricted to such memory. Indeed, C++ doesn't even know what is and isn't read-only memory.

As well as this being derivable from all the usual rules, with no exception [lol] made for dynamically-allocated objects, the standards mention this explicitly (albeit in a note):

[C++03: 5.3.4/1]: The new-expression attempts to create an object of the type-id (8.1) or new-type-id to which it is applied. The type of that object is the allocated type. This type shall be a complete object type, but not an abstract class type or array thereof (1.8, 3.9, 10.4). [Note: because references are not objects, references cannot be created by new-expressions. ] [Note: the type-id may be a cv-qualified type, in which case the object created by the new-expression has a cv-qualified type. ] [..]

[C++11: 5.3.4/1]: The new-expression attempts to create an object of the type-id (8.1) or new-type-id to which it is applied. The type of that object is the allocated type. This type shall be a complete object type, but not an abstract class type or array thereof (1.8, 3.9, 10.4). It is implementation-defined whether over-aligned types are supported (3.11). [ Note: because references are not objects, references cannot be created by new-expressions. —end note ] [ Note: the type-id may be a cv-qualified type, in which case the object created by the new-expression has a cv-qualified type. —end note ] [..]

There's also a usage example given in [C++11: 7.1.6.1/4].

Not sure what else you expected. I can't say I've ever done this myself, but I don't see any particular reason not to. There's probably some tech sociologist who can tell you statistics on how rarely we dynamically allocate something only to treat it as non-mutable.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • I've just never seen a `new const ...` anywhere (but I guess I knew tbh, just thought it was interesting) – Luchian Grigore Apr 01 '14 at 23:11
  • @LuchianGrigore: I can't say I've ever used it myself, but I don't see why it should be treated as any less valid than a `const` object created "on the stack", as it were. – Lightness Races in Orbit Apr 01 '14 at 23:11
  • 1
    There is also example on 7.1.6.1/4 – zch Apr 01 '14 at 23:11
  • C++03 5.3.4 §1 is exactly the same as C++11's with only difference that C++03 doesn't say anything about "over-aligned types". – LihO Apr 01 '14 at 23:13
  • 1
    Is this valid? `int x = 0; const int *px = new (&x) const int(0); x = 1; cout << *px;` ? – Johannes Schaub - litb Apr 01 '14 at 23:18
  • @JohannesSchaub-litb: No, I don't think so. It's masking an effective `const_cast`. One min. ([It compiles, at least](http://coliru.stacked-crooked.com/a/7b491bf64aef07f5)) – Lightness Races in Orbit Apr 01 '14 at 23:18
  • Well, I don't know the answer.. I don't even know whether `*px` reads from an `int` or `const int` object. – Johannes Schaub - litb Apr 01 '14 at 23:29
  • @JohannesSchaub-litb: That's the question innit – Lightness Races in Orbit Apr 01 '14 at 23:30
  • 2
    @JohannesSchaub-litb: I think that `x = 1;` is undefined behavior, because you're using an object that no longer exists (its storage has been reused). And naturally you have to destroy `*px` and use placement new to put an object of the original type, `int`, back before it goes out of scope. – Ben Voigt Apr 01 '14 at 23:33
  • @BenVoigt: I think `[C++11: 3.8/1]` agrees with you (in that it says the object lifetime ends when the storage is "_reused_ or released"). Ah, but, then again, `[C++11: 3.8/7]` disagrees. The name `x` now refers to the new object, not the old one that died. – Lightness Races in Orbit Apr 01 '14 at 23:34
  • @LightnessRacesinOrbit: I know about that rule, that's what prompted my comment. The only thing I'm not sure of is whether `x = 1;` creates a new `int` object in the location, reusing the storage where the `const int` object briefly was. Objects with trivial constructors can be brought into existence just by using them. But ISTR reusing the storage of a const object is also forbidden... – Ben Voigt Apr 01 '14 at 23:35
  • @BenVoigt: I don't think assignment could ever be considered to create a new object. Other than a temporary, that is. – Lightness Races in Orbit Apr 01 '14 at 23:36
  • @BenVoigt: Yeah, 3.8/7 says the type of the original cannot be `const`-qualified (which, here, it isn't, of course). This is actually quite interesting; I think, after all this, it _does_ mask an implicit `const_cast` and subsequent UB. – Lightness Races in Orbit Apr 01 '14 at 23:37
  • Oh, but 3.8p7 explicitly allows this case. "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, ... 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: ... the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and the type of the original object is not const-qualified..." – Ben Voigt Apr 01 '14 at 23:38
  • @BenVoigt: I just said all that ;) – Lightness Races in Orbit Apr 01 '14 at 23:38
  • @LightnessRacesinOrbit: I think a read a version of your comment which mentioned 3.8p1 but not 3.8p7. Was there one? – Ben Voigt Apr 01 '14 at 23:39
  • @BenVoigt: The one that mentions 3.8/1 originally stopped before "Ah, but then again", but that was some time ago and before all the subsequent comments :) I'm going to ask and self-answer this question tomorrow, if you two don't beat me to it. I'm sure I have it cracked and I think it's interesting. – Lightness Races in Orbit Apr 01 '14 at 23:40
  • 3.8p9 is somewhat relevant: "Creating a new object at the storage location that a const object with static, thread, or automatic storage duration occupies or, at the storage location that such a const object used to occupy before its lifetime ended results in undefined behavior." and then there's 3.8p1 "The lifetime of an object of type T begins when storage with the proper alignment and size for type T is obtained, and if the object has non-trivial initialization, its initialization is complete." – Ben Voigt Apr 01 '14 at 23:41
  • @BenVoigt: That scenario is the inverse of this scenario, isn't it? – Lightness Races in Orbit Apr 01 '14 at 23:42
  • Here's the solution: `x` indeed refers to the new, `const` object, per 3.8p7. And then 7.1.6.1p4 "any attempt to modify a const object during its lifetime (3.8) results in undefined behavior" – Ben Voigt Apr 01 '14 at 23:42
  • @LightnessRacesinOrbit: I see people mentioning the example in 7.1.6.1p4, but not the rule. The rule doesn't actually require a `const_cast` to precede undefined behavior. – Ben Voigt Apr 01 '14 at 23:44
  • @BenVoigt: No, course not. Modifying a `const` object is UB. Doesn't matter how you got around the compiler safeties to do it. `union`s would be another way to do it (though in asserting that such a technique wouldn't already be inherently undefined, I'm assuming that [say] a `const int` and an `int` are layout-compatible, of which I'm uncertain). Fair play for making it explicit, though: you're certainly correct in that nobody had directly cited that. – Lightness Races in Orbit Apr 01 '14 at 23:46
  • @LightnessRacesinOrbit: Within the constructor during construction of a `const` object, `*this` is not const-qualified, right? And it can be safely used to mutate the object (its lifetime hasn't yet begun). But `this` can also be saved off into a non-const pointer... another way to get to UB without using `const_cast` explicitly. – Ben Voigt Apr 01 '14 at 23:47
  • @BenVoigt: Yeah (12.1/4), yeah (7.1.6.1/4, 3.8) and yeah. – Lightness Races in Orbit Apr 01 '14 at 23:50
  • @LightnessRacesinOrbit: Saving `this` before the object becomes `const` sounds like a very subtle way to subvert the type system. Wonder if one can find an environment where it is expressed as a runtime failure (as opposed to merely UB which is potential failure). That would be a good prank, I think. – Ben Voigt Apr 01 '14 at 23:52
  • http://stackoverflow.com/q/22808067/560648 (covers the basics). Leaving the prank for 1st April 2015 :) – Lightness Races in Orbit Apr 02 '14 at 10:08
  • @BenVoigt *I think that x = 1; is undefined behavior, because you're using an object that no longer exists*. Doesn't it create the object by the write again (as an `int` object), like it does when saying `*(int*)malloc(sizeof(int)) = 0`? – Johannes Schaub - litb Apr 02 '14 at 21:43
  • Johannes, doing that would be illegal, you aren't allowed to create an new object that overlaps a const object, during the lifetime of the const object. But no there isn't an int recreated, because the name x now refers to the new const object. – Ben Voigt Apr 02 '14 at 21:45
  • @BenVoigt "you aren't allowed to create a new object that overlaps a const object"? Is that the case here? Why doesn't the lifetime of the `const int` object end when its storage is reused? – Johannes Schaub - litb Apr 02 '14 at 21:49
  • 3.8p9 talks about storage that used to be occupied by a const object. – Ben Voigt Apr 02 '14 at 22:26
  • @JohannesSchaub-litb: Perhaps let's take this up on the new question I raised for this issue. Besides, all quotes relevant to your doubts are covered both there and in comments above. – Lightness Races in Orbit Apr 03 '14 at 00:13
0

My way of looking at this is:

  • X and const X and pointers to them are distinct types
  • there is an implicit conversion from X* to const X*, but not the other way around
  • therefore the following are legal and the x in each case has identical type and behaviour

    const X* x = new X; const X* x = new const X;

The only remaining question is whether a different allocator might be called in the second case (perhaps in read only memory). The answer is no, there is no such provision in the standard.

david.pfx
  • 10,520
  • 3
  • 30
  • 63