When to Use Const and Const Reference in Function Args

What is the difference between a const reference and normal parameter?

The difference is more prominent when you are passing a big struct/class:

struct MyData {
int a,b,c,d,e,f,g,h;
long array[1234];
};
void DoWork(MyData md);
void DoWork(const MyData& md);

When you use use 'normal' parameter, you pass the parameter by value and hence creating a copy of the parameter you pass. If you are using const reference, you pass it by reference and the original data is not copied.

In both cases, the original data cannot be modified from inside the function.


EDIT:

In certain cases, the original data might be able to get modified as pointed out by Charles Bailey in his answer.

When to use const and const reference in function args?

Asking whether to add const is the wrong question, unfortunately.

Compare non-const ref to passing a non-const pointer

void modifies(T ¶m);
void modifies(T *param);

This case is mostly about style: do you want the call to look like call(obj) or call(&obj)? However, there are two points where the difference matters. If you want to be able to pass null, you must use a pointer. And if you're overloading operators, you cannot use a pointer instead.

Compare const ref to by value

void doesnt_modify(T const ¶m);
void doesnt_modify(T param);

This is the interesting case. The rule of thumb is "cheap to copy" types are passed by value — these are generally small types (but not always) — while others are passed by const ref. However, if you need to make a copy within your function regardless, you should pass by value. (Yes, this exposes a bit of implementation detail. C'est le C++.)

Compare const pointer to non-modifying plus overload

void optional(T const *param=0);
// vs
void optional();
void optional(T const ¶m); // or optional(T param)

This is related to the non-modifying case above, except passing the parameter is optional. There's the least difference here between all three situations, so choose whichever makes your life easiest. Of course, the default value for the non-const pointer is up to you.

Const by value is an implementation detail

void f(T);
void f(T const);

These declarations are actually the exact same function! When passing by value, const is purely an implementation detail. Try it out:

void f(int);
void f(int const) {/*implements above function, not an overload*/}

typedef void C(int const);
typedef void NC(int);
NC *nc = &f; // nc is a function pointer
C *c = nc; // C and NC are identical types

C++: const reference, before vs after type-specifier

No difference as const is read right-to-left with respect to the &, so both represent a reference to an immutable Fred instance.

Fred& const would mean the reference itself is immutable, which is redundant; when dealing with const pointers both Fred const* and Fred* const are valid but different.

It's a matter of style, but I prefer using const as a suffix since it can be applied consistently including const member functions.

Difference between reference and const reference as function parameter?

Without the error message, I'm not exactly sure what the compiler might be complaining about, but I can explain the reason logically:

In the line:

bar(foo()); 

The return value of foo() is a temporary A; it is created by the call to foo(), and then destructed as soon as bar() returns. Performing a non-const operation (i.e. an operation that changes the temporary A) doesn't make sense, as the object A is destructed right afterwards.

Looking a little more, this is a virtual dup of this question:

How come a non-const reference cannot bind to a temporary object?

which has an excellent answer.

Use of 'const' for function parameters

The reason is that const for the parameter only applies locally within the function, since it is working on a copy of the data. This means the function signature is really the same anyways. It's probably bad style to do this a lot though.

I personally tend to not use const except for reference and pointer parameters. For copied objects it doesn't really matter, although it can be safer as it signals intent within the function. It's really a judgement call. I do tend to use const_iterator though when looping on something and I don't intend on modifying it, so I guess to each his own, as long as const correctness for reference types is rigorously maintained.

benefits of passing const reference vs values in function in c++ for primitive types

In every ABI I know of, references are passed via something equivalent to pointers. So when the compiler cannot inline the function or otherwise must follow the ABI, it will pass pointers there.

Pointers are often larger than values; but more importantly, pointers do not point at registers, and while the top of the stack is almost always going to be in cache, what it points at may not. In addition, many ABIs have primitives passed via register, which can be faster than via memory.

The next problem is within the function. Whenever the code flow could possible modify an int, data from a const int& parameter must be reloaded! While the reference is to const, the data it refers to can be changed via other paths.

The most common ways this can happen is when you leave the code the complier can see while understanding the function body or modify memory through a global variable, or follow a pointer to touch an int elsewhere.

In comparison, an int argument whose address is not taken cannot be legally modified through other means than directly. This permits the compiler to understand it isn't being mutated.

This isn't just a problem for the complier trying to optimize and getting confused. Take something like:

struct ui{
enum{ defFontSize=9;};
std:optional<int> fontSize;
void reloadFontSize(){
fontSize=getFontSizePref();
fontSizeChanged(*fontSize),
}
void fontSizeChanged(int const& sz){
if(sz==defFontSize)
fontSize=std:nullopt;
else
fontSize=sz;
drawText(sz);
}
void drawText(int sz){
std::cout << "At size " << sz <<"\n";
}
};

and the optional, to whom we are passing a reference, gets destroyed and used after destruction.

A bug like this can be far less obvious than this. If we defaulted to passing by value, it could not happen.

Why the need for both const reference and const member function?

  1. return a const reference to prevent the function to change the returning value
  2. a const member function that cannot modify the object. in this case, it would be like this

This could have been a bit more clearer, I'll try my shot at explaining it better.

Returning a const reference prevent the returned object to be mutated by callers.

Here's an example:

// let get_name be `const std::vector<std::string>& get_name()`

np1.get_name().size(); // ok, size is a const function of vector
np1.get_name().push_back("hello"); // ERROR! push_back is not a const function of vector

So indeed, a caller cannot change the name vector. The return type is const qualified.

However, if the function get_name itself is not const qualified (not the return type), then it is allowed to change name from the class itself.

You see, member functions receive a hidden this parameter, which is a pointer to the object being called on. The pointer can either point to a const object, or a mutable object. Here's the comparison:

// returning const ref, callers cannot change the name vector
const std::vector<std::string>& get_name() {
// The function receive a `this` that points to a mutable,
// we can change the name from the inside
this->name.push_back("another");
return name;
}

// the `this` pointer points to const -----v---v
const std::vector<std::string>& get_name() const {
this->name.push_back("another"); // ERROR! Cannot mutate member of const object
return name;
}

Const-qualified member function are really useful for the caller, as it knows that whenever this function is called, its state won't change.

For example, not only you know that the vector::size() function won't mutate the vector, but the compiler guarantees it since it's a const qualified member function.

And for the last bit, the code you posted here:

vector<string> get_name() const { return name; } 

This will not return a reference, but will make a copy. The caller can mutate the copy however it wants, but cannot mutate name itself.

Here's an example of a copy mutated:

auto name_copy = np1.get_name();

name_copy.push_back("another name"); // works, we mutated the copy by adding an element

C++ Difference Between Const Reference to Non Const Object and Non Const Reference to Non Const Object

"What is the purpose for the "const" keyword for a reference if the object it is referencing is not a const object?"
The purpose is to prevent that reference being used to modify the object it is referencing.

int i = 42; // non const object
const int &r1 = i; // const reference to non const object
r1 = 6 * 9; // error, r1 cannot be used to modify i;

When to use const references over const value in function?

For values and objects smaller than 8 bytes, it makes sense to pass by value rather than reference. Even an 8 byte object is cheaper to copy than to pass the reference, then access the object.

References are not guaranteed to be implemented as pointers. In fact as per §8.3.2/4:

It is unspecified whether or not a reference requires storage


At what threshold should you prefer a const reference over a const value?

The situation is pretty simple:

  • use const references when you just want to read the value
  • use value when you will always end up making a copy of the object inside the function
  • use reference when you want to modify the object inside the function


Related Topics



Leave a reply



Submit