Skip to content

Last updated:2026-10-13

C++ const Explained: Constants, const Pointer vs Pointer to const

About the Author

WeChat official account:

C++ const Explained: Constants, const Pointer vs Pointer to const | C++那些事

1. The Meaning of const

A const type is a type declared with the type qualifier const; the value of a const-type variable or object cannot be updated.

2. The Role of const

  • It can define constants
const int a=100;
  • Type checking

    • The difference between const constants and #define macro-defined constants:

      A const constant has a type, so the compiler can perform safety checks; a #define macro definition has no data type — it is mere string replacement, so no safety check is possible.Thanks to the two experts who pointed out the problem here; see: issue

    • const constants support all types.

    • In other cases it is just a const-qualified variable; do not confuse it with a constant.
  • Preventing modification, providing protection and making the program more robust

void f(const int i){
    i++; // error!
}
  • Saving space and avoiding unnecessary memory allocation

    • A const initialized with an integral/enum type and a constant expression (i.e. a constant expression) behaves in use much more like a compile-time constant: if you never take its address and only use its value, it is usually optimized into an immediate, just like #define; only taking its address or an odr-use across translation units puts it into memory, and there is only one copy in the whole program.
    • After #define expands into a literal, it is compiled into instructions as an immediate; the instructions themselves occupy memory too, so multiple uses mean multiple occurrences (see the discussion in issue #5).
    • Note: not all const variables are constant expressions — only those of integer/enum type initialized with a constant expression are; other const variables are merely const-qualified variables (e.g. floating point, user-defined types); constexpr is the correct way to express compile-time constants.

3. const Objects Are Local to a File by Default

Note: non-const variables are extern by default. To make a const variable accessible in other files, it must be explicitly specified as extern in the file.

Accessing a variable not modified by const across different files

// file1.cpp
int ext;
// file2.cpp
#include<iostream>

extern int ext;
int main(){
    std::cout<<(ext+10)<<std::endl;
}

Accessing a const constant across different files

// extern_file1.cpp
extern const int ext=12;
// extern_file2.cpp
#include<iostream>
extern const int ext;
int main(){
    std::cout<<ext<<std::endl;
}

Summary:
You can see that variables not modified by const need no explicit extern declaration! A const constant, however, requires an explicit extern declaration and must be initialized! Because a constant cannot be modified after its definition, it must be initialized at definition time.

4. Defining Constants

const int b = 10;
b = 0; // error: assignment of read-only variable ‘b’
const string s = "helloworld";
const int i,j=0 // error: uninitialized const ‘i’

The above contains two errors: + b is a constant and cannot be changed! + i is a constant and must be initialized! (Because a constant cannot be modified after its definition, it must be initialized at definition time.)

5. Pointers and const

There are four const forms related to pointers:

const char * a; // 指向const对象的指针或者说指向常量的指针。
char const * a; // 同上
char * const a; // 指向类型对象的const指针。或者说常指针、const指针。
const char * const a; // 指向const对象的const指针。

Summary:
If const is on the left side of *, then const modifies the variable the pointer points to, i.e. what the pointer points to is a constant;
if const is on the right side of *, const modifies the pointer itself, i.e. the pointer itself is a constant.

Another way to read it
Read the English from right to left, taking "to" as the divider: what comes before "to" describes the pointer's trait, and what comes after "to" describes the target's trait

const char * p; //p is a pointer to const char
char const * p; //同上
char * const p; //p is a const pointer to char
const char * const p; //p is a const pointer to const char
When the pointer itself is qualified with const, the pointer can no longer change the address it points to
When the target's type is char, the content can be modified through the pointer, e.g. *char='y';
When the target's type is const char, the content cannot be modified through the pointer

Concrete usage is as follows:

(1) Pointer to a constant

const int *ptr;
*ptr = 10; // error

ptr is a pointer to a const object of type int; the const qualifies the int type — the type of the object ptr points to — not ptr itself, so ptr need not be given an initial value. But you cannot modify the pointed-to object's value through ptr.

Besides, you also cannot use a void* pointer to hold the address of a const object; you must use a pointer of type const void* to hold the address of a const object.

const int p = 10;
const void * vp = &p;
void *vp = &p; // error

Another key point: it is allowed to assign the address of a non-const object to a pointer to a const object.

Assigning the address of a non-const object to a pointer to a const object:

const int *ptr;
int val = 3;
ptr = &val; // ok

We cannot modify the value of val through the ptr pointer, even though it points to a non-const object!

We cannot use a pointer to a const object to modify the underlying object; however, if the pointer points to a non-const object, the object it points to can be modified in other ways. The value pointed to by a const pointer can be changed — just not through the const object pointer! Modify it as follows:

int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;

Summary:
1. For a pointer to a constant, the object's value cannot be modified through the pointer.
2. You cannot use a void* pointer to hold the address of a const object; you must use a pointer of type const void* to hold the address of a const object.
3. Assigning the address of a non-const object to a pointer to a const object is allowed; to modify the value of the pointed-to object you must do it through other means, not directly through the current pointer.

(2) Constant pointer

A const pointer must be initialized, and the value pointed to by a const pointer can be modified, but its pointing cannot.

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    ptr = &num1; // error! const指针不能修改指向!
    cout<<*ptr<<endl;
}

The code produces a compile error: a const pointer cannot change what it points to.

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    *ptr = 1;
    cout<<*ptr<<endl;
}

Nothing goes wrong with this code; it normally outputs 1.

Finally, when the address of a const constant is assigned to ptr, since ptr points to a variable rather than a const constant, an error is reported: a const int* -> int * error!

#include<iostream>
using namespace std;
int main(){
    const int num=0;
    int * const ptr=&num; // error! const int* -> int*
    cout<<*ptr<<endl;
}

If the above is changed to const int *ptr, or to const int *const ptr, both work fine!

(3) Constant pointer to a constant

Having understood the previous two cases, this one is fairly easy to understand:

const int p = 3;
const int * const ptr = &p; 

ptr is a const pointer that then points to a const object of type int.

6. Using const in Functions

const qualifying a function's return value

This is basically the same in meaning as const qualifying ordinary variables and pointers:

(1) const int

const int func1();

This by itself is meaningless, because the return value is itself assigned to another variable!

(2) const int*

const int* func2();

The content pointed to by the pointer is immutable.

(3) int *const

int *const func2();

The pointer itself is immutable.

const qualifying function parameters

(1) The passed-in parameter and the pointer itself are immutable inside the function — meaningless!

void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变

This indicates that the parameter cannot be modified inside the function body, but here it has no significance: var is itself a formal parameter and will not change within the function. The same applies when the passed-in formal parameter is a pointer.

When an input parameter uses "pass by value", the function automatically creates a temporary variable to copy the parameter, and that input parameter never needed protection in the first place, so do not add const.

(2) The content pointed to by the parameter pointer is a constant and immutable

void StringCopy(char *dst, const char *src);

Here src is an input parameter and dst is an output parameter. After adding const to src, if a statement inside the function body tries to change the content of src, the compiler will point out the error. This is one of the effects of adding const.

(3) The parameter is a reference, to improve efficiency while preventing modification.

void func(const A &a)

For parameters of non-inner data types, a function declared like void func(A a) is bound to be rather inefficient, because the function body will create a temporary object of type A to copy the parameter a, and the temporary object's construction, copying and destruction all take time.

To improve efficiency, the function can be declared as void func(A &a), because "pass by reference" merely borrows the parameter's alias and needs to create no temporary object.

But the function void func(A &a) has a drawback:

"pass by reference" may modify the parameter a, which we do not expect. Solving this problem is easy — just add const, and the function finally becomes void func(const A &a).

By analogy, should void func(int x) be rewritten as void func(const int &x) to improve efficiency? Not necessary at all: parameters of inner data types involve no construction or destruction, and copying is very fast — "pass by value" and "pass by reference" are nearly equal in efficiency.

Summary:
1. For input parameters of non-inner data types, you should change "pass by value" to "pass by const reference" in order to improve efficiency. For example, change void func(A a) to void func(const A &a).

2. For input parameters of inner data types, do not change "pass by value" to "pass by const reference". Otherwise you neither achieve the efficiency gain nor keep the function easy to understand. For example, void func(int x) should not be changed to void func(const int &x).

This settles two interview questions:

  • If a function takes a pointer, does that pointer need const, and what is the difference between placing const at different positions of the pointer;
  • If the function's parameter is an instance of a complex type, what is the difference between passing it by value or by reference, and when should const be added to the passed reference parameter.

7. Using const in Classes

In a class, any function that does not modify data members should be declared const. If, while writing a const member function, you accidentally modify a data member, or call another non-const member function, the compiler will point out the error, which undoubtedly improves the robustness of the program.

A member function specified with the const keyword is called a constant member function. Only constant member functions are qualified to operate on constants or constant objects; member functions not specified with the const keyword cannot be used to operate on constant objects.

const member variables of a class must be initialized through the initialization list, as shown below:

class Apple{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
};

Apple::Apple(int i):apple_number(i)
{

}

const objects can only access const member functions, while non-const objects can access any member function, including const member functions.

For example:

// apple.cpp
class Apple
{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
    void take(int num) const;
    int add();
    int add(int num) const;
    int getCount() const;

};
// apple.cpp
Apple::Apple(int i) : apple_number(i)
{
}
int Apple::add(int num)
{
    take(num);
    return 0;
}
int Apple::add(int num) const
{
    take(num);
    return 0;
}
void Apple::take(int num) const
{
    std::cout << "take func " << num << std::endl;
}
int Apple::getCount() const
{
    take(1);
    //    add(); // error
    return apple_number;
}
int main()
{
    Apple a(2);
    cout << a.getCount() << endl;
    a.add(10);
    const Apple b(3);
    b.add(100);
    return 0;
}
// main.cpp

Compile: bazel run basic_content/const/class_const/first_example:main

At this point an error is reported: getCount() is a const member function, and overload resolution inside it matches only the const version (int add(int num) const); only after that const version is commented out would the error come from calling the non-const add(). That is: const objects can only access const member functions, while non-const objects can access any member function (see issue #338).

When the call is changed to:

const Apple b(3);
b.add(); // error

This proves that const objects can only access const member functions.

Besides initializing const constants through the initialization list as above, we can also use the following methods:

First: combine the constant definition with static, that is:

static const int apple_number

Second: initialize it outside:

const int Apple::apple_number=10;

Of course, if you compile with C++11, you can initialize directly at the declaration and simply write:

static const int apple_number=10;
// 或者
const int apple_number=10;

Both are supported in C++11!

Just add -std=c++11 when compiling!

This brings up static; here is a brief note on it:

In C++, static static member variables cannot be initialized inside the class. Inside the class there is only a declaration; the definition must be outside the class definition body, usually initialized in the class's implementation file.

Declared in the class:

static int ap;

Used in the class implementation file:

int Apple::ap=666

For this item, C++11 does not allow declaration together with initialization — that is, the usage above.

FAQ

What is the difference between a pointer to const and a const pointer?

const int* p is a pointer to a constant: the value cannot be changed through it. int* const p is a constant pointer: the pointer itself cannot be reassigned. Rule of thumb: const left of * protects the value, right of * protects the pointer.

Can a const object call a non-const member function?

No. A const object may only call const member functions, so the compiler can guarantee the object state is untouched. A non-const object can call both.

Should I use const or #define for constants?

Prefer const: it is type-checked, respects scoping and can be debugged. #define is a preprocessor text substitution with no type information.

Comments