24

Let's say we have a class called object.

int main(){
    object a;
    const object* b = &a;
    (*b); 
}

Question: b is a pointer to const, but the object it is pointing to is actually not a constant object. My question is, when we dereference the pointer "b" why do we get a constant object instead of what it is actually pointing at which is normal non constant object.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Ashim
  • 877
  • 2
  • 12
  • 22
  • 14
    Because you declared it that way. – Mad Physicist May 22 '18 at 02:14
  • 5
    When you dereference a pointer, the type of the result is based on the type of the pointer. – Barmar May 22 '18 at 02:15
  • 6
    Because that's the whole purpose of such pointers - to create a constant access path to a (potentially non-constant) object. – AnT stands with Russia May 22 '18 at 02:16
  • 1
    Relevant: [What is the difference between const int*, const int * const, and int const *?](https://stackoverflow.com/a/1143272/3195314) – alseether May 22 '18 at 13:01
  • MadPhysicist: That comment is probably *very* helpful to the OP. – mastov May 22 '18 at 17:27
  • @mastov: I think it is helpful although it could use some further explanation, e. g. "because the language requires it work this way for this type of declaration." I don't see a question about the reasons behind the language design. – David Foerster May 22 '18 at 19:22
  • `const` and *constant* are two very different things. `const` means read-only. *Constant* refers to something that can be computed at compile time. For example, `const int r = rand();` is perfectly valid, though the value of `r` clearly can't be determined until execution time. – Keith Thompson May 27 '18 at 22:44

10 Answers10

26

Because that's how the built-in dereference operator * works in C++. If you dereference a pointer of type T *, you get an lvalue of type T. In your case T is const object.

Neither * operator, not the pointer itself cares (or knows) that the object the pointer is pointing to is actually non-constant. It just can't be any other way within the concept of static typing used by C++ language.

The whole purpose of const-qualification at the first (and deeper) levels of indirection is to provide you with an ability to create restrictive access paths to objects. I.e. by creating a pointer-to-const you are deliberately and willingly preventing yourself (or someone else) from modifying the pointee, even if the pointee is not constant.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Oh so it does something similar to static casting, just like how a parent pointer points to child but when we use the pointer it will only call functions of the parent class unless we have virtual functions or dynamic cast then it will do runtime check. Thanks for the answer – Ashim May 22 '18 at 02:40
  • 2
    Not really. Declaring a pointer `const` simply says to the compiler: "don't let a user of this pointer change the object it points to". It says nothing about the object itself. – Paul Sanders May 22 '18 at 05:11
  • i.e. low-level const. – Joseph D. May 22 '18 at 08:10
  • In C, a compiler given a pointer like `int const *restrict p = &someInt;` would be entitled to assume that the value of `someInt` will not be changed while `p` holds its address. Although C++ hasn't adopted the `restrict` qualifier, it's certainly possible for a statically-typed language to care about whether the value of something targeted by a qualified pointer may change. – supercat May 22 '18 at 18:44
11

The type of an expression is just based on the declared types of the variables in the expression, it can't depend on dynamic run-time data. For instance, you could write:

object a1;
const object a2;
const object *b;
if (rand() % 2 == 0) {
    b = &a1;
} else {
    b = &a2;
}
(*b);

The type of *b is determined at compile-time, it can't depend on what rand() returns when you run the program.

Since b is declared to point to const object, that's what the type of *b is.

Barmar
  • 741,623
  • 53
  • 500
  • 612
6

Because the const keyword means 'you can not modify that object' rather than 'the object can not be modified'.

This is useful when you pass an object to some function to use the object's value only, but but not to modify it:

// We expect PrintMyString to read the string
// and use its contents but not to modify it

void PrintMyString(const char *string);

void myfunction(int number)
{
    // build and print a string of stars with given length
    if(number>0 && number<=16]
    {
        char string[17];
        int i;
        for(i=0, i<number; i++)
            string[i] = '*';
        string[number] = '\0';

        PrintMyString(string);
    }
}

The function PrintMyString will get an array of characters, but the array will be passed as 'read only' to the function. The array is certainly modifiable within the owning function, but the PrintMyString can only read what it gets and not alter the contents.

CiaPan
  • 9,381
  • 2
  • 21
  • 35
2
const object* b = &a;

This means: b is a pointer to a const (read-only) object

Doesn't say anything about the const-ness of a. It just means b has no right to modify a.

Also known as low-level const.


Contrary to the above, top-level const is when the pointer itself is const.
object *const b = &a;   // b is a const pointer to an object

b = &some_other_object; // error - can't assign b to another object
                        // since b is a const pointer

*b = some_value;        // this is fine since object is non-const
Joseph D.
  • 11,804
  • 3
  • 34
  • 67
2

There are plenty of other answers here, but I'm driven to post this because I feel that many of them provide too much detail, wander off topic, or assume knowledge of C++ jargon that the OP almost certainly doesn't have. I think that's unhelpful to the OP and others at a similar phase of their careers so I'm going to try to cut through some of that.

The situation itself is actually very straightforward. The declaration:

const SomeClassOrType *p = ...

simply says that whatever p is pointing to cannot be modified through that pointer, and that is useful to ensure that code that gains access to that object via p doesn't modify it when it isn't supposed to. It is most often used in parameter declarations so that functions and methods know whether or not they can modify the object being passed in (*).

It says nothing at all about the const-ness of what is actually being pointed to. The pointer itself, being just a simple soul, does not carry that information around with it. It only knows that, so far as the pointer is concerned, the object pointed to can be read from but not written to.

Concrete example (stolen from CiaPan):

void PrintString (const char *string);

char string [] = "abcde";
const char *p = string;
PrintString (p);

void PrintString (const char *ptr_to_string)
{
    ptr_to_string [0] = 0;   // oops!  but the compiler will catch this, even though the original string itself is writeable
}

You can just pass string to PrintString directly of course, same difference, because the parameter is also declared const.

Another way to look at this is that const is information provided by the programmer to help the compiler check the correctness / consistency of the code at compile time. It lets the compiler catch mistakes like the one above, and perhaps perform better optimisations. By the time you actually run your code, it is history.


(*) The modern idiom is probably to use a const reference, but I don't want to muddy the waters by throwing that into the main discussion.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Please don't criticize other answers unless it's useful for explaining yours (e.g. "something not yet mentioned is X", "I respectfully disagree with the existing answers because Y"). – Jared Smith May 22 '18 at 13:32
  • @Jared Did I not do that? Perhaps not very well. What I should perhaps have said is that many of the other answers given here provide too much detail, wander off topic, or assume knowledge of C++ jargon that the OP almost certainly doesn't have. That's unhelpful here and that's what I was trying to address. Looking again, AnT's answer is pretty good, but it needs an example. – Paul Sanders May 22 '18 at 13:52
  • Seems a bit confrontational to me, but I like your answer overall and did upvote. – Jared Smith May 22 '18 at 14:17
  • @Jared Yes, I could certainly have phrased it better. I can be a bit like that sometimes, sorry, will try harder. – Paul Sanders May 22 '18 at 14:25
  • @Jared I have amended my post, thank you for pointing out the error of my ways. – Paul Sanders May 22 '18 at 14:33
1

Simply put,

  1. For plain poiters, the result of unary operator * is a reference to pointed-to type.

  2. So, if the type pointed-to is const-qualified, the result is a reference to a constant.

  3. References to constants can bind to any objects, even to mutable objects of otherwise the same type. When you bind a constref to a value, you promise to not ad-hoc modify that value through this refence (you still can do it explicitly though, cast it to a non-const reference).

  4. Likewise, const-pointers can point to objects still modifiable otherwise.

bipll
  • 11,747
  • 1
  • 18
  • 32
1

By writing

const object* b = &a;

you declare that b is a pointer (*) to a const of type object, to which you then assign the address of a. a is of type object (but not const); you are permitted to use the adress of the non-const object in place of an adress of an const object.

When you dereference * b however, the compiler can only go according to your declaration - thus *b is a const object (however you can still modify a as you like, so beware of thinking that the object b points to cannot change - it mere cannot be changed via b)

CharonX
  • 2,130
  • 11
  • 33
0
const object* b = &a;

b will treat what it points to as const, i.e. it cannot change a

object* const b = &a;

b itself is const, i.e. it cannot point to other object address, but it can change a

gchen
  • 1,173
  • 1
  • 7
  • 12
  • So I cannot modify the object pointed by `b` through `b`, but if I modify `a`, will dereferencing `b` show the new value? – roschach May 22 '18 at 08:37
  • @FrancescoBoi yes, it will show the new value. `a` isn't const, so `a` can be changed by itself (or by other non-const references/pointers), and dereferencing `b` gets whatever is in `a`. – gchen May 22 '18 at 11:06
0

Because at run-time it might be a const object, meaning that any operations performed on the dereference must be compatible with const. Ignore the fact that in your example it has been pointed to a non-const object and cannot point to anything else, the language doesn't work like that.

0

Here’s a specific example of why it is the way it is. Let’s say you declare:

int a[] = {1, 2, 3};
constexpr size_t n_a = sizeof(a)/sizeof(a[0]);

extern int sum( const int* sequence, size_t n );
sum_a = sum( a, n_a );

Now you implement sum() in another module. It doesn’t have any idea how the original object you’re pointing to was declared. It would be possible to write a compiler that tagged pointers with that information, but no compilers in actual use today do, for a number of good reasons.¹

Inside sum(), which might be in a shared library that cannot be recompiled with whole-program optimization, all you see is a pointer to memory that cannot be altered. In fact, on some implementations, trying to write through a pointer to const might crash the program with a memory-protection error. And the ability to reinterpret a block of memory as some other type is important to C/C++. For example, memset() or memcpy() reinterprets it as an array of arbitrary bytes. So the implementation of sum() has no way to tell the provenance of its pointer argument. As far as it’s concerned, it’s just a pointer to const int[].

More importantly, the contract of the function says that it’s not going to modify the object through that pointer.² It could simply cast away the const qualifier explicitly, but that would be a logic error. If you’re declaring a pointer const, it’s like engaging the safety on your gun: you want the compiler to stop you from shooting yourself in the foot.

¹ Including extra instructions to extract the address from a pointer, extra memory to store them, compatibility with the standard calling convention for the architecture, breaking a lot of existing code that assumes things like long being able to hold a pointer, and the lack of any benefit.

² With the pedantic exception of mutable data members.

Davislor
  • 14,674
  • 2
  • 34
  • 49