简体   繁体   中英

Understanding const

If you want to write an iterator, you usually write

T* operator->() const;

My problem is understanding this "const" with pointers and reference.

For instance, you can write the following struct:

struct A{
   int* x;

   A(int& x0):x{&x0}{}
   int* ptr() const {return x;}
   int& ref() const {return *x;}
};

And you can use it this way:

int x = 10;
const A a{x};

int& r = a.ref();
int* p = a.ptr();

*p = 4;
cout << x << endl;   // prints 4

r = 25;
cout << x << endl;  // prints 25

But why this compiles and works right (at least with g++ and clang). Why?

As I defined

const A a{x};

this "a" is const. So when I call

int* p = a.ptr();

I am calling ptr() with a const object, so the internal pointer A->x must be "int * const". But I am returning a "int *" without const. Why is this correct?

And what happens with the reference? If I call A::ref() with a "const A", what's the type this function returns? Something like "int& const"??? <--- I suppose this is the same as "int&".

Thanks for your help.

There is a different between bitwise const and logical const.

When you have a const A , you can't modify its int* member. But there's a difference between modifying the member itself ( which int that x points to) and modifying through the member (the value of the int that x points to). Those are different kinds of const . In the simplest case:

struct Ptr { int* p; };

int i = 42;
const Ptr ptr{&i};
*ptr.p = 57; 

ptr.p still points to i , nothing changed there, so the const mechanic is enforced. But it's not logically const since you still changed something through a const object. The language doesn't enforce that for you though. That's up to you as the library writer.

If you want to propagate const -ness, you just provide an interface that is logically const :

int const* ptr() const {return x;}
int const& ref() const {return *x;}
//  ^^^^^

Now, users can't modify through your const A both bitwise (can't change what x points to) and logically (can't change that value of that pointee either).

But why this compiles and works right (at least with g++ and clang). Why?

Because the program is well formed and has defined behaviour. Const correctness was not violated.

I am calling ptr() with a const object, so the internal pointer A->x must be "int * const". But I am returning a "int *" without const. Why is this correct?

Because it is completely OK to make copies of const objects. Those copies need not to be const. Copying an object does not make modifications to the original object (assuming there is no user defined copy constructor that does silly things).

And what happens with the reference? If I call A::ref() with a "const A", what's the type this function returns?

int& ref() always returns int& . Just like int* ptr() always returns int* .

Something like "int& const"???

There is no such thing like int& const . References cannot have top level qualifiers (they can never be re-assigned).

In struct A , when you make an instance of it const , you make the pointer constant, but that doesn't automatically make the pointed-to object constant .

Declaring something like const A a(ref); is basically equivalent to invoking the following code:

struct A_const {
    int * const x;

    A(int& x0):x{&x0}{}
    int* ptr() const {return x;}
    int& ref() const {return *x;}
};

If you remember your pointer rules, this means that x is a constant pointer, which means it cannot be made to point to something else (it's functionally similar to a reference, but can be null ), but critically, the int that it is pointing to is not constant , which means nothing stops you from writing something like this:

int val = 17;
const A a(val);
*(a.val) = 19; //Totally valid, compiles, and behaves as expected!
int val2 = 13;
//a.val = &val2; //Invalid, will invoke compile-time error

This is also the reason why std::unique_ptr<int> and std::unique_ptr<const int> represent different objects.

If you want the pointed-to object to not be modifiable on a const object, you need to enforce that in the code itself. Since functions can be overloaded on the basis of whether the source object is const or not, that's pretty easy:

struct A {
    int * x;

    A(int& x0):x{&x0}{}
    int * ptr() {return x;}
    int & ref() {return *x;}
    int const* ptr() const {return x;}
    int const& ref() const {return *x;}
};

int val = 17;
A a(val);
a.ref() = 19;//Okay.
*a.ptr() = 4;//Okay.

const A b(val);
b.ref() = 13;//Compile error
*b.ptr() = 17;//Compile error

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM