C++: Const correctness and pointer arguments

33,992

Solution 1

You have it backwards:

const int * intPtr1; // Declares a pointer whose contents cannot be changed.
int * const intPtr2; // Declares a pointer that cannot be changed.

The following const is indeed unnecessary, and there's no reason to put it in a function declaration:

void someFunc1(int * const arg);

However, you might want to put it in the function implementation, for the same reason that you might want to declare a local variable (or anything else) const - the implementation may be easier to follow when you know that certain things won't change. You can do that whether or not it's declared const in any other declarations of the function.

Solution 2

Well it is not meant for the caller but for the code inside the someFunc1. So that any code inside someFunc1 wont accidentally change it. like

void someFunc1(int *arg) {
  int i = 9;
  arg = &i;  // here is the issue
  int j = *arg;
}

Lets do some case study:

1) Just making the pointed value const

void someFunc1(const int * arg) {
int i = 9;
*arg = i; // <- compiler error as pointed value is const
}

2) Just making the pointer const

void someFunc1(int * const arg) {
int i = 9;
arg = &i; // <- compiler error as pointer is const
}

3) Right way to use const if variables involved can be const:

void someFunc1(const int * const arg) {
    int i = 9;
    *arg = i; // <- compiler error as pointed value is const
    arg = &i; // <- compiler error as pointer is const
}

This should clear all doubts. So I already mentioned it is meant for the function code and not for the caller and you should use the most restrictive of the 3 cases i mentioned above.

EDIT:

  • Even in declarations of functions its a good practice to declare const. This will not only increase readability but also the caller will be aware of the contract and has more confidence regarding immutability of arguments. (This is required bcoz you generally share your header files so caller might not have your implementation c/cpp file)
  • Even compiler can point out better if both declaration and definitions are in sync.

Solution 3

You've got your logic the wrong way round. You should read the type backwards, so const int * is a pointer to a const int and int * const is a const pointer to an int.

Example:

void foo() {
    int a = 0;
    int b = 0;

    int * const ptrA = &a;
    *ptrA = 1;
    ptrA = &b; ///< Error

    const int * ptrB = &a;
    *ptrB = 1; ///< Error
    ptrB = &b;

    const int * const ptrC = &a;
    *ptrC = 1; ///< Error
    ptrC = &a; ///< Error
}

To elaborate and show why you would want your function parameter to be a const int * you might want to indicate to the caller that they must pass in an int because you as a function want to change the value. Consider this code for instance:

void someFunc1(const int * arg) {
    // Can't change *arg in here
}

void someFunc2(int * arg) {
    *arg = 5;
}

void foo() {
    int a = 0;
    someFunc1(&a);
    someFunc2(&a);

    const int b = 0;
    someFunc1(&b);
    someFunc2(&b); ///< *** Error here. Must pass in an int not a const int.
}

Solution 4

Yes, you are correct (ignoring the fact that you got them the wrong way around)- there is no sense in taking non-reference const parameters. In addition, there is no sense in returning non-reference const values.

Solution 5

You have it the wrong way:

const int * intPtr1; // Declares a pointer whose contents cannot be changed.
int * const intPtr2; // Declares a pointer that cannot be changed.

Generally speaking its easier to reason about constness when writting that expression slightly different: const int* is the same type as int const *. In that notation the rules are much clearer, const always applies to the type preceding it, therefore:

int const * intPtr1; // Declares a pointer to const int.
int * const intPtr2; // Declares a const pointer to int.
int const * * const * complexPtr; // A pointer to const pointer to pointer to const int

When the type is written with a leading const, the const is handled as if it was written after the first type, so const T* becomes T const *.

void someFunc2(int * arg);

Is therefore not redundant, since someFunc2 may change the contents of arg, while someFunc1 may not. void someFunc3(int * const arg); would be redundant (and ambigous) though

Share:
33,992

Related videos on Youtube

Ben
Author by

Ben

Updated on January 11, 2020

Comments

  • Ben
    Ben over 4 years

    I understand that a const pointer can be declared a couple ways:

    const int * intPtr1; // Declares a pointer that cannot be changed.
    int * const intPtr2; // Declares a pointer whose contents cannot be changed.
    
    // EDIT: THE ABOVE CLAIMS ARE INCORRECT, PLEASE READ THE ANSWERS.
    

    But what about the same principles within the context of function arguments?

    I would assume that the following is redundant:

    void someFunc1(const int * arg);
    void someFunc2(int * arg);
    

    Since someFunc 1 and 2 do a pass-by-value for the pointer itself, its impossible for someFunc1 to change the value of the original pointer, in a given call to the function. To illustrate:

    int i = 5;
    int * iPtr = &i;
    
    someFunc1(iPtr); // The value of iPtr is copied in and thus cannot be changed by someFunc1.
    

    If these are true, then there is no point in ever declaring a function with a 'const int * ptr' type arg, correct?

    • Alan Stokes
      Alan Stokes over 12 years
      You have your declarations the wrong way round. "const int *" is a changeable pointer to an unchangeable int. "int * const" is an unchangeable pointer to a changeable int.
    • Ben
      Ben over 12 years
      Edited the question so that trigger happy googlers won't run off misinformed. I left the mistake in so that the answers still have that context though.
  • Xeo
    Xeo over 12 years
    Non-reference const return values are even harmful.
  • ruakh
    ruakh over 12 years
    But that doesn't explain why you'd write void someFunc1(const int * arg);. Note that predeclaring someFunc1 as void someFunc1(int * arg); still lets you define it as void someFunc1(int * const arg) { ... } if you want; the const-ness of the parameter variable itself doesn't affect the actual function signature.
  • ruakh
    ruakh over 12 years
    @Xeo: Could you elaborate on that, please? I didn't know they had any effect at all!
  • James McNellis
    James McNellis over 12 years
  • Xeo
    Xeo over 12 years
    @ruakh: Move semantics for one. T const&& can not be moved-from, even though you know it's a temporary that will be destroyed soon.
  • ruakh
    ruakh over 12 years
    @JamesMcNellis: Thank you! Interestingly, from the link there, it turns out that Herb Sutter takes the opposite stance from DeadMG and Xeo: he contends that when using return-by-value, you should use const, at least for non-builtin return types.
  • ruakh
    ruakh over 12 years
    @Xeo: Ah, thanks. That also might explain why Herb Sutter's article disagrees: it may have been written before C++11's move semantics were established.
  • James McNellis
    James McNellis over 12 years
    @ruakh: That article was written over a decade ago, so yes, well before move semantics were established.
  • Ben
    Ben over 12 years
    Now I look like a fool...Thanks for the concise explanation and affirming that there is indeed an unnecessary const in one of those definitions!
  • havexz
    havexz over 12 years
    @ruakh pls go through the updated ans. That should ans your concern.
  • ruakh
    ruakh over 12 years
    Sorry, no, your updated answer still doesn't answer the question. (See Mike Seymour's answer, above, for the correct answer.)
  • havexz
    havexz over 12 years
    "However, you might want to put it in the function implementation, for the same reason that you might want to declare a local variable (or anything else) const" = "I already mentioned it is meant for the function code and not for the caller "...nyways the point is clear thats the bottom line...
  • ruakh
    ruakh over 12 years
    I guess I just have a strange preference for answers that actually answer the question!
  • havexz
    havexz over 12 years
    @ruakh :) well I too hv same taste as you. well from question it looks the guy knows const but wondering what it is doing in context of functions. So thats the ans is all about. Nyways i edited to reflect some more light.
  • dynamic
    dynamic over 11 years
    is there any difference between const int * intPtr1 and int const * intPtr1; ?
  • Mike Seymour
    Mike Seymour over 11 years
    @yes123: No difference at all. const qualifies the thing before it, unless it's at the beginning, in which case it qualifies the first thing. So in both cases, it qualifies the int, not the pointer. int * const would qualify the pointer.
  • Kindred
    Kindred over 5 years
    @MikeSeymour: Wow that's so clear, I wonder there is a question asking about this? (I was finding the question can be solved by your comment.)
  • gkhaos
    gkhaos over 3 years
    Common trick: read any c-like declaration from right to left: int const * intPtr1 --> intPtr1 is a pointer to a const (read-only) integer