Master C++ const once and for all

Introduction

In C++, const keyword is short for constant. A constant variable/object doesn’t change/mutate during program runtime. Constantness also means read-only access to a variable/object.

You can simply avoid using constant in your C++ program and make money with no problem at all. For example, the Python language is very viable and doesn’t support constant variables. However, if you use const in C++ you get the below advantages:

  • Better performance: compiler injects nitro in your code engine.
  • Avoiding bugs: You set a rule, A is constant, if mistakenly you try to change A, the compiler empties an ice bucket on you.
  • Easy to read and use API: you create a library that accepts an object like an iPhone 12, the user of the library will be happy if it is received as const rather than being tested for 100 meters drop test.

Primitive types

Basic types can be defined constant, which means don’t you dare change them :

const int i=1;
i=2; // Error: Hey YOU, stop changing i

const double j=1.1;
double k = j+1; // Great, just read j

const std::string s="Cheer up!";
std::cout<< s; // Good boy :), just read s
s = "HA HA"; // 3 pages of Error: don't mess with s, pal!

Objects of class

A constant object doesn’t change during runtime:

class Car{
    public:
    string color;
    Car(string c):color(c){};
};

const Car myCar{"blue"};

string color = myCar.color; // OK: I tell you my car's color
myCar.color = "white"; // Error: don't chage my car's specs

So, the constructor is the only way to set your const object, myCar.

Const is contagious

A const member function doesn’t change the object of a class. So, we have this rule you can only call a const member function of a const object.

So, if I say my car is const, I cannot do something to change it:

class Car{
    string driverSeat;

    public:
    Car(string ds):driverSeat(ds){};
    
    void AdjustDriverSeat(string ds){
        driverSeat=ds;}
};

int main(){

  const Car myCar{"Seat is set for Sorush"};
  myCar.AdjustDriverSeat("Move it forward"); //Error: don't change the car   
}

However, I can do things that do not change the state of my car i.e. I can call const member functions:

class Car{
    string driverSeat;
    
    public:
    
    Car(string ds):driverSeat(ds){};
    
    void PrintDriverSeat() const{ // note const !!!!
        std::cout<<driverSeat;
    }
    void Drive() const {/* drive my car but don't crash it */}
};


int main(){

 const Car myCar{"Seat is set for Sorush"};
 myCar.PrintDriverSeat(); // you can look at my car
 myCar.Drive(); // Drive Safe :)
    
}

Function Argument

Sometimes we want to pass an object to a function and say “hey function, read it but be nice and do not change it”. You have some ways as follows.

Pass by const reference

I lend my Bose headphones to my friend, “Rose”:

class Headphones{
    public:
    void SmashIt(){}
    void Skype() const {} // note const!!!
};

void RoseUse(const Headphones& h){
    h.Skype(); // amazing girl
    h.SmashIt(); // Error: Not a good pal :(
 }

int main(){

  Headphones myHeadphones;
  RoseUse(myHeadphones);
}

with const & I am sure Rose can not smash my headphones.

Pass by value

We know when we pass to a function by value, a copy constructor is called and a new variable/object is created.

So, I give my headphones to Rose, she will buy the same Bose headphones. She does whatever with her new headphones, she may dip it in a cup of tea like a teabag, who cares! it’s not my headphones:

class Headphones{
    public:
    void BreakIt(){}
    void DipInTea(){}
    void Skype(){}
};

void RoseUse(Headphones hers){
    hers.Skype();
    hers.BreakIt();
    hers.DipInTea();
}

int main(){
 
 Headphones myHeadphones;
 RoseUse(myHeadphones);
// my heaphone is not touched   
}

Just note that in this case if you defined void RoseUse(const Headphones hers) it will just limit the clone, hers, and wouldn’t add value to the purpose: my headphones mustn’t change.

Pass by const pointee

You can also pass the address of the object, but emphasise that it is read-only with const.

Therefore, I tell Rose the address of my headphone: “on my desk, please don’t compromise our friendship, bring it back as you received it”.

class Headphones{
    public:
    void SmashIt(){}
    void Skype() const{} // note const!!!
};

void RoseUse(const Headphones* HeadphonesAddress){
    HeadphonesAddress->Skype(); // OK, my BFF
    HeadphonesAddress->SmashIt(); // Error: Noooo :(
}

int main(){

 Headphones myHeadphones;
 RoseUse(&myHeadphones);
// my heaphone is not touched
}

Return member

You can return an alias to a member via const function but the return must be const too. I told you const is contagious.

class Car{
    string color;
    public:
    Car(string c):color(c){};
    const string& GetColor () const {
        return color;
    }
};

If you drop const from const string& GetColor(), you get an error.

So, if I give my car as const to Steven, he can see it and enjoy the view but cannot spray paint its body:

void StevenCheck(const Car& car){
    std::cout<<car.GetColor(); // OK :)
    car.GetColor()="red";// Error: don't change my car
}

int main(){
    Car myCar{"blue"};
    StevenCheck(myCar);    
}

const overload

You can overload a member function just by const:

class Car{
    string color;
    public:
    Car(string c):color(c){};
    const string& GetColor () const {
        return color;
    }

    string& GetColor () {
        return color;
    }
};

int main(){

    Car jackCar{"white"};
    jackCar.GetColor() = "red"; // non-const GetColor() used

    const Car roseCar{"blue"};
    // const GetColor() is called
    std::cout<<roseCar.GetColor(); // OK: read color
    roseCar.GetColor()="red"; //  Error: Don't change it
}

Member pointer

A pointer member of a constant object will be constant but not its target i.e. it will be shallow constant. Can you guess the output of the below example?

#include <memory>
#include <vector>
#include <iostream>

struct A{
    void print() const { std::cout<<"const\n";}
    void print() {std::cout<<"non const\n";}
};

struct B {
  B() {
    p=new A{}; 
    up=std::make_unique<A>(); 
    v.push_back(A{}); 
  }
  A* p;
  std::unique_ptr<A> up;
  A a;
  std::vector<A> v;
};

int main() {
  B b;
  b.p->print();
  const B& bref=b;
  bref.a.print();
  bref.v[0].print();
  bref.p->print();
  bref.up->print();
  //Uncomment below, you get error of changing constant pointer. 
  //bref.p=nullptr; 
}
// Output:
// b.p->print(); non const
// bref.a.print(); const
// bref.v[0].print(); const
// bref.p->print(); non const
// bref.up->print(); non const

For bref, the raw pointer p and unique pointer up become constants but their pointees are not constant. For the STL container member, v, the constantness of bref is kept to the level of accessing v elements.

Tags ➡ C++

Subscribe

I notify you of my new posts

Latest Posts

Comments

0 comment