6

This question is different from:


I wrote a class Test like this.

class Test {

    private:

        int *p;

    public:

        //constructor
        Test(int i) {
            p = new int(i);
        }

        Test & operator = (const Test &rhs) {
            delete p;
            p = new int(*(rhs.p));
            return *this;
        }

};

When the parameter rhs of the operator function is itself (i.e. Test t(3); t = t;), delete p; also changes the pointer p of rhs. Why is this allowed?

C++ standard (N3092, "3.7.4.2 Deallocation functions") says

If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined.

(Note: delete-expression internally calls a deallocation function. So this excerpt is related with delete operator.)

So I think delete p; may change the member p of rhs though rhs is a const reference.

Someone may insist that "to render a pointer invalid is not to change the value of a pointer" but I don't find such a statement in the standard. I doubt there is a possibility that the address pointed by rhs's p has been changed after delete p; in operator =(*).

(*): Whether or not this situation can be reproduced on popular compilers doesn't matter. I want a theoretical guarantee.


Supplement:

I've changed delete p; to delete rhs.p;, but it still works. Why?

Full code here:

#include <iostream>

class Test {

    private:

        int *p;

        //print the address of a pointer
        void print_address() const {
            std::cout << "p: " << p << "\n";
        }

    public:

        //constructor
        Test(int i) {
            p = new int(i);
        }

        Test & operator = (const Test &rhs) {

            print_address(); //=> output1
            delete rhs.p;
            print_address(); //=> output2

            p = new int(*(rhs.p));
            return *this;

        }

};

int main() {

    Test t(3);
    t = t;

}

In this case, it is guaranteed that p is invalidated. But who guarantees invalidate != (change the value)? i.e. Does the standard guarantee that output1 and output2 are the same?

matn
  • 117
  • 6
  • That's a side effect of your own change. But `const` doesn't mean that the object `rhs` will not change over time, it just means that you (accessing `rhs`) cannot change it. – Matthieu Brucher Mar 13 '19 at 16:40
  • There’s no ”also” — `*this` is the same object as `rhs`. – molbdnilo Mar 13 '19 at 16:42
  • 4
    You have a `const` reference parameter, but not a `const` object. That means that you're not allowed to use that reference to change the object. It doesn't mean that the referred-to object is immutable. – molbdnilo Mar 13 '19 at 16:53
  • 4
    `this` is not `const` and `this` is the object being operated on. The fact that `rhs` is the same as `*this` is not relevant. Think of the annoyance it would be if you could not change a value because somewhere else in the program there was a `const` reference to it. How would this be tracked? The book-keeping required would be insane. – user4581301 Mar 13 '19 at 16:53
  • @molbdnilo Ah, I understand now. Thank you. – matn Mar 13 '19 at 16:55
  • 2
    OT.: deleting `this->p` and then assigning a copy of `*rhs.p` while `&rhs == this` looks like a guarantee to shoot in your own foot for me. ;-) I sometimes saw a previous check for `this != &rhs` and this example illustrates very nicely why this makes very sense. – Scheff's Cat Mar 13 '19 at 16:56
  • @user4581301 I think you are totally right. Thank you. – matn Mar 13 '19 at 16:56
  • @Scheff, yes indeed, that's undefined behavior. – Matthieu Brucher Mar 13 '19 at 16:58
  • @Scheff Yes. I know this code is not good. This is referred to as a bad code in *Effective C++ 3rd Edition*. – matn Mar 13 '19 at 17:00
  • Wait. Changing `delete p;` to `delete rhs.p;` still works. Why? – matn Mar 13 '19 at 17:01
  • You can still `delete` a const pointer. You aren't changing the value of the pointer, you are ending the life of what it points to. – Caleth Mar 13 '19 at 17:03
  • @matn because `delete p` doesn't change the `p` - and only `p` itself is const for const reference. And even if `p` was a const pointer, you'd still be able to call `delete` on it. – SergeyA Mar 13 '19 at 17:03
  • Its a bug in the Standard. The similar bug exists in the C Standard. – Language Lawyer Mar 13 '19 at 23:43

2 Answers2

6

So I think delete p; may change the member p of rhs though rhs is a const reference.

  1. No. delete p; doesn't change p. Invalidation is not modification.

  2. Regardless, having a const reference to an object (rhs) does not by any means prevent the referred object form being modified. It merely prevents modification through the const reference. In this case we access the object through this which happens to be a pointer to non-const, so modification is allowed.

Someone may insist that "to render a pointer invalid is not to change the value of a pointer" but I don't find such a statement in the standard.

The behaviour of delete expression is specified in [expr.delete]. Nowhere in that section does it mention that the operand is modified.

Becoming invalid is specified like this:

[basic.compound]

... A pointer value becomes invalid when the storage it denotes reaches the end of its storage duration ...

Note that it is the value that becomes invalid. The pointer still has the same value because the pointer was not modified. The value that the pointer had and still has is simply a value that no longer points to an object - it is invalid.


Supplement: I've changed delete p; to delete rhs.p;, but it still works. Why?

Answer 2. From previous question no longer applies, but answer 1. does. delete rhs.p; does not modify rhs.p.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    For later reader: the statement "A pointer value becomes invalid when ..." can be found in N4713(C++20) and N4659(C++17) but not in N3092(C++11) or N4140(C++14). – matn Mar 13 '19 at 18:11
  • _Note that it is the value that becomes invalid. The pointer still has the same value_ Wait. Does it become invalid or the pointer has the same value? It can't be both. – Language Lawyer Mar 13 '19 at 23:27
  • @LanguageLawyer it can be both, and is. The same value that used to be valid is now invalid. It is still the same value. I find it hard to express this unambiguously in English. – eerorika Mar 14 '19 at 00:09
  • @eerorika in a discussion under some CWG issue Mike Miller told that the value of an object could be changed only by a store. Which means that freeing memory generates a store into each pointer pointing into the freed region. Storing invalid pointer value into it. – Language Lawyer Mar 14 '19 at 00:12
  • @LanguageLawyer how did you come to the conclusion that stores are generated into each pointer? – eerorika Mar 14 '19 at 00:16
  • @eerorika if a value could be changed only by a store and pointer values become invalid when a region of memory is freed, then this invalidation is performed by a store – Language Lawyer Mar 14 '19 at 00:17
  • @LanguageLawyer but why do you think that the value changed? – eerorika Mar 14 '19 at 00:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/189985/discussion-between-language-lawyer-and-eerorika). – Language Lawyer Mar 14 '19 at 00:18
3

Calling delete on a member pointer frees the memory the pointer points to but does not change the pointer itself. Thus, it does not change the bitwise contents of the object, thus it can be done in a const member.

C++ only cares about bitwise const (of the object the method is invoked on). Not logical const. If no bits in the object change, then all is well - const wise - as far as the C++ language is concerned. It does not matter whether the logical behaviour of the object is changed (for example by changing something member pointers point to). That's not what the compiler checks for.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • Is the phrase "but does not change the pointer itself" guaranteed in the standard? As I cited in my question, the standard says *delete-expression* invalidates the related pointers. Why can we say "To invalidate a pointer never change the value of the pointer"? – matn Mar 13 '19 at 17:14
  • 3
    @matn I have not checked the standatd text. But I would be *very* surprised if it said anything about the pointer itself changing. A pointer is just a variable holding an address of something else, and to change it would require work to be done, and in C++ you don't pay for what you don't use/ask for. So, silently paying the cost of changing the bits of the pointer when doing a `delete` would be *very* un-C++-like. As far as invalidation goes, that does not require the pointer to change. That just means it's no longer valid for you to dereference it. – Jesper Juhl Mar 13 '19 at 17:21
  • Thank you. Your explanation sounds so natural. It seems I'm too paranoid. – matn Mar 13 '19 at 17:33
  • _does not change the pointer itself_ Are you sure? https://timsong-cpp.github.io/cppwp/n4659/basic.stc#4 says that the values of pointers become _invalid pointer values_. If a pointer value was _pointed to_ an object and then became _invalid pointer value_, how it didn't change? – Language Lawyer Mar 14 '19 at 08:01