c++ const keyword explained in detail

c++ const is used very commonly. If you have ever written any code in C++, you will definitely have used const and come across it. Needless to say, is a very useful and important feature. Using c++ const can greatly improve the robustness of your code. It is also a great way to document to other developers that the object marked as const should not be modified. On the other hand, omission of the const keyword in places where it is needed results in more brittle, error-prone code.

In a nutshell, const  (constant) means that a property or value cannot change. 

More...

The Golden Rule for reading const variable declaration

Before moving on, let me give you the golden tip for understanding cont variable declaration. I did not come up with this: I got it from C++ primer. This sentence made reading and understand const variable declarations so much more easier for me. Hopefully, it will also be the same for you.

It can be easier to understand complicated pointer or reference declarations if you read them from right to left

You might be asking: "wait, the quote above is regarding pointers and reference declarations". Personally, I think you can apply this golden nugget towards understanding const declarations. In the upcoming sections, I will be making a lot of references to the statement above. If you have any additional suggestions or tips that allowed you to grasp C++ const, please let me know. Before moving on however, we need to cover two fundamental concepts. If unexplored, it will hinder our ability to fully understand const. Trust me, you will thank me for it later.

Object vs Reference

Before moving on, I want you to throw out all the preconceived notions derived from Object-oriented programming. For example, to JavaScript developers, an object may be something like this.

var iAmAnObject = {};  // This is an object to a JavaScript developer. 

Let me make it clear: references != objects. First of all, what is an object? From a computer's perspective, an object is a region of memory that has a type. These types could be complex types such as a class, or a primitive type such as a short or an int.

However, to a computer, even something like int number = 42; is an object. Why? Because it is a region of memory that has a type. Bytes of memory is assigned to that value which is of type int.

Okay, we covered objects, but what is a reference then? A reference is an alias. It is simply just another name for an object that already exists. Think of it as a parasite that latches onto an already existing object. Once that object is destroyed, it refers to NULL aka nothing. Sure, after the object it is referring to is destroyed, it can refer to another object to survive. The memory surrounding it will be collected once the function finishes executing and is popped off the call stack. Unless, it was created dynamically using the new keyword. Then, the developer will manually have to reclaim the memory via the delete keyword. But the major point here is that references cannot exist without objects.

Example: Object and Reference

Sometimes, the best way to explain concepts is by writing code. I think this statement applies to the current situation.


int &referenceVal = 1337;  // this is an error. Reference initializer must be an object

int number = 10;

int &validDecl = number; // This is a reference. 

int &anotherReference = number; // Another reference


In the example above, we have two references to number. So, de-referencing both these reference variables will result in "10" being outputted to the console. It is the same as this: Lets say that my friends call me "troll". If they say "hey Jay" or "hey "Troll", hypothetically speaking, I would respond to both calls. Why? Because they both point towards me. Might seem like a dumb example, but that is essentially what references are: an alias.

On the other hands, objects are data with a type that takes up memory. The reason why the first line yields an error is that 1337 is not an object sitting in memory. In line one, we were simply attempting to assign an arbitrary value 1337 that doesn't even exist in memory. However, once a value of 10 is assigned to numbers, it becomes an object. It takes up memory, and has a type. A reference simply is an alias for an object. validDecl and anotherReference are aliases for number, which has a value of 10.

Reference vs Pointers

You might be saying: "just get to the point already!". Please bear with me just a little bit longer. Trust me, we are going somewhere! In the previous section, we established that references are not objects. References are merely aliases for an existing object. The next thing you need to understand is the difference between references and pointers.

Variables and Pointers Defined

We first need to know what a pointer is. I will later write a detailed series on pointers, but for now, lets rely on a short definition. According to tutorialspoint,

A pointer is a variable whose value is the address of another variable, i.e., direct address of the memory location

Immediately we can see the difference. Both reference and pointers are variables. While this may seem elementary, let us first look at the definition of a variable. According to ntu.edu.sg

A variable is a named location that can store a value of a particular type

However, what are the commonalities and the differences between a reference and a pointer? This is going to take a lot of focus, so please bear with me.

Difference between Pointers and References

Okay, a reference is an alias. Pointers are variables, but are references also variables? Yes! They do have their differences, albeit, being very similar. What are the differences then?

1. References are named constants for an address.

Okay, we are going to let that sink in for a bit. This is why we were getting an error when we tried to assign 1337 to the reference value. Because 1337 is an undeclared constant: It is not a memory address! Another caveat is that because it is a named constant for an address, it needs to be initialized.

int &uninitializdRef; // Error. You need to initialize this jawn!

2. Reference variables reference and de-reference its values implicitly.

As you may be well aware, we de-reference pointer variables with the star (*) character to access a value stored in memory. In another words, developers need to do this explicitly in their code. Reference variables, on the other hand, do not need to be explicitly de-referenced.


int aNum = 10;
int &reference = aNum;              // No explicit & in front of aNum required.
std::cout << aNum << std::endl;     // No explicit de-refencing needed. All done implicitly.
int *aNumPtr = &aNum;               // Explicit & to indicate that i am storing the memory address of aNum
std::cout << aNumPtr << std::endl;  // prints the memory address of aNum
std::cout << *aNumPtr << std::endl; // Print the value 10


​Values Marked as Const

Now that we have gone through all the concepts behind references, variables and pointers, let us return to the main topic. Const variables are self-explanatory. It means that the value stored in a particular variable cannot be changed. Hopefully, when I start talking about references, the contents in previous sections will start making a lot of sense.

const std::string immutable = "I cannot be changed"; 

immutable = "Fail ... remember you can't change me";  // Error

While this may look very straight forward, trust me, it doesn't end here. There is a good reason for going into references, objects and pointers. With each example that we go through, I recommend you as a reader, to type out all the example yourselves. Just like learning a language, in order to become proficient, you need to take steps to try it yourself.

Pointers Marked as Const​

From my experiences learning C++, const in it of itself wasn't difficult to understand. The problems arose when const was coupled withpointers. In case you are not aware,pointers are a source of headache for many aspiring C/C++ developers. In the near future, I will write a series of blog posts covering pointers.

Once const pointers are set to point at something, cannot point at another object. Remember to initialize the pointer, otherwise you won't be able to compile your program.


int number = 3;              
int anotherNum = 1;
const int *pNumber = &number;  // pNumber is a pointer to an int that is constant
pNumber = &anotherNum;         // valid. pNumber is NOT a constant pointer. So I can have it point to something else.


Const Pointer to a Const Value

The example explicitly tells the compiler that the value that pNumber's value cannot change. In another words, we cannot do the following operation.

*pNumber = anotherNum; // Cannot assign to readonly type 'int const'

Thought the actual value itself cannot be changed, the part of the memory that the *pNumber points at can change. Why? Because pNumber is a pointer to a constant int. In another words, the pointer itself is not constant, meaning we are able to have pNumber point at another object. Remember, that an object is data that is stored in memory and has a type.

Const Pointer to a Const Value

These are the truly immutable objects. For the purposes of this tutorial, I will define an object as something that contains data and takes up memory. Let me show you what a constant pointer to a constant int looks like.

const int constNumber = 6;
const int * const constPointerToConstValue1 = &constNumber; 
int const * const constPointerToConstValue2 = &anotherNum;
int const const * constPointerToConstValue3 = constPointerToConstValue2;
constPointerToConstValue1 = constPointerToConstValue2; // ERROR!


In order to understand this easily, read the declaration from right to left. Applying this to line 2,3 and 4, we will read it like this.

2. constPointerToConstValue1 is a constant pointer to an int that is constant.
3. constPointerToConstValue2 is a constant pointer to a constant int.
4. constPointerToConstValue3 is a pointer that is constant that is a constant int.


Seriously, if there is any point I would like for a reader to take away, it would be this.

Rules of Engagement: Using const effectively

From my own personal experience, as well as what I have seen in other pieces of code, I have made a set of rules. These rules are not absolute. However, they should serve as a solid guideline, enabling readers to soberly assess whether to use it or not.

1. Protect your privates!

I apologize if the title turned you off. I tried to make it as catchy as possible so that it will stick in your head. In traditional OOP, getters and setters are used to encapsulated member variables of a class. Therefore, in getters, it is common sense to protect your private/member variables by attaching const at the end.


public std::string GetName() const {

   this->name = "thisWillResultInCompileTimeError";  // Compile time error thanks to const

   return this->name;   

}


Without constant variables, some developer can come in and change member variables (whether accidental or intentional). This will add another layer of safety to your code.

2. Define the scope via inclusion/omission of extern

It is important to determine the scope of the variable. Sometimes, you might just use it in one file or even in a single method and not use it outside. Other times, when you are developing a complex application, you may want other files to be able to access that constant files. In this case, it is important to use the extern keyword. By default, const objects are local to the file. extern allows us to define a "single instance" of a const variable. Since the example from C++ Primer (5th Edition) is so good, I will once again, be taking a leaf out of this book.

// file_1.cc defines and initializes a const that is accessible to other files extern const int bufSize = fcn(); // file_1.h extern const int bufSize; // same bufSize as defined in file_1.cc

The example above may first appear to be confusing. Let me break it down. The extern keyword in file_1.cc is declared and assigned a variable, which is the return value of fcn(). Just assume that its an arbitrary number such as 10, or 1.

To provide greater clarity, I have uploaded a small code sample to illustrate exactly how extern is working. Click here for the source code.

Things to avoid when using const

A wise man once said, that with great power comes even greater responsibility. Const, when misused, can create nightmares. Fortunately, the compiler catches most of the simple user errors. However, misuse of const in design decisions can make certain APIs downright dangerous to use. In this section, I will go through a list of things to avoid when using const. This list is not absolute. It will grow/shrink over time.

Uninitialized const variables

Before we move on, let me clarify one thing. In C++, generally, it is good practice to initialize variables as they are declared. This makes your code more readable, therefore, easier to debug. It makes sense so far right?

What is the key fundamental fact in regards to constant variables? Once they are declared, they cannot be changed. Think about what will happen if the compiler allowed uninitialized constant variables. Therefore, the compiler is kind enough to spare us from that nightmare.


const int iMadeAMistake;                           // Thank you compiler for reminding me that I didn't initialize the variable.

const std::string privateKey = "#$SMF_13S231";     // I cannot change this once declared.

int number = 1337; 

const int &numberAddress = &i;                     // This is valid. We can bind a const int address to a plain int. 

const int &anotherNumberAddress = &numberAddress * 3 // Also valid. This is a reference to a constant value that wont change.

int &nonConstantReference = numberAddress * 2;    // error here. this is a non-constant reference.


Source code

The C++ const source is available on github. Some of the inline examples have not been uploaded. If you have any suggestions on improving the comprehensiveness of the tutorial, please reach out to me. Thank you once again for taking time to read through and I hope this article shed light the const keyword in C++.

 

About the Author Jay

I am a programmer currently living in Seoul, South Korea. I created this blog as an outlet to express what I know / have been learning in text form for retaining knowledge and also to hopefully help the wider community. I am passionate about data structures and algorithms. The back-end and databases is where my heart is at.

follow me on: