tl;dr

There are many discussions between reference and pointers, it’s not always true to say “use references when you can, and pointers when you have to“. We should pay attention to the lifetime of the input parameters, choose the one that is safer and more efficient according to the use case.

How to pass a parameter?

Before we talk about const reference vs. pointer in C++ class constructor, let’s take a quick look at different ways of passing a parameter to a function.

1
2
3
4
5
6
7
8
9
10
11
// 1. Passing by value.
void DoSomething(int a) {}

// 2. Passing by const reference.
void DoSomething(const int& a) {}

// 3. Passing by pointer.
void DoSomething(int* a) {}

// 4. Passing by const pointer.
void DoSomething(const int* a) {}
# parameter object copied? original object mutable?
1 passing by value ✔️ X
2 passing by const reference X X
3 passing by pointer X ✔️
4 passing by const pointer X X

As it can easily tell from the table above, passing by value introduces an extra object copy, while passing by pointer makes the original object mutable.

If you just want to pass some data, do some work with its value without updating it, you can choose #2 or #4, (don’t pick #1 as it’s could be expensive especially for large object, don’t use #3 as you could mistankenly update its value). Between #2 and #4, I’m leaning to #2 because reference is always non-nullable.

If you want to pass a parameter and update its value, pick #3 obviously.

I rarely use #1 in daily work.

Is passing const pointer always safe?

Not really.

1
2
3
4
5
6
7
8
class Person {
public:
Person(const std::string& name) : name_(name) {}
const std::string& GetName() const { return name_; }

private:
const std::string& name_;
};

The above example uses const reference in a class constructor, it looks perfectly normal, right? What if we initialize Person lie this:

1
2
3
4
void SomeFunction() {
Person person("Ox333");
std::cout << person.GetName(); // Error!
}

When creating person, the inside field name_ is just an alias of the temporary string Ox333. Once person is created, the temporary string goes out of scope, when it tries to print out the name (the content that the alias refers to), it doesn’t exist anymore. The behavior becomes unpredictable!

On OnlineGDB, it prints empty result. Live example: https://onlinegdb.com/HyOIEOS-U.

How to avoid this?

A straightforward fix is to store the value inside the class, so it’s always accessible, however, it requires an extra copy.

1
2
3
4
5
6
7
8
class Person {
public:
Person(const std::string& name) : name_(name) {}
const std::string& GetName() const { return name_; }

private:
std::string name_; // Store the value inside the class!
};

Another fix, is to store the const pointer.

1
2
3
4
5
6
7
8
9
10
class Person {
public:
// The ownership is outside the class.
Person(const std::string* name) : name_(name) {}
const std::string& GetName() const { return *name_; }

private:
// Not owned, shouldn't be null.
const std::string* name_;
};

By doing so, if the caller tries to create a person with a temporary string, the compiler would complain it takes the address of a temporary variable. The unpredictable behavior is avoided proactivey by the compiler.

1
2
3
4
5
6
7
8
std::string GetString() {
return "A string";
}

void SomeFunction() {
Person person(&GetString()); // Error: taking address of temporary [-fpermissive]
std::cout << person.GetName();
}

Live example: https://onlinegdb.com/B15NNuHWL.

Can we do better?

We can use const reference to enforce the field name_ to be non-nullable. By doing so, we don’t need to worry if the field name_ is a nullptr or not.

1
2
3
4
5
6
7
8
9
class Person {
public:
// The ownership is outside the class.
Person(const std::string* name) : name_(*name) {}
const std::string& GetName() const { return name_; }

private:
const std::string& name_; // Use const reference to enforce non-nullable.
};

Check the live example: https://onlinegdb.com/Hy9hOurbU.

To sum up

There is no such a golden rule telling you when to choose const reference or pointer, what we can do is to understand how it works, choose the most suitable solution according to the specific case.