C++ Const and Pass-By-Value

Introduction

Sometimes we would encounter library interfaces that take const inputs by value. It turns out that such interface reflects lack of thinking in library design and implementation.

In this blog post, I would like to briefly discuss about the best practice for the situation that has both const and pass-by-value.

Const and Pass-By-Value Interface and Implementation

Suppose we are going to create a shared library libfoo.so that contains a bar function which takes in one int value as input and returns an int value as output. We could have the following two interface options.

foo.hpp
1
int bar(int v);
foo.hpp
1
int bar(int const v);

Since pass-by-value makes a copy of the variable, when the user reads the the interfaces, the user knows that both interface will not alter the original value passed to the function. So the question now becomes which interface we should choose in terms of designing the library. Because the interface should not assume too much about how the function will be implemented, the best interface should be the first one.

With the first interface, we could come up with two implementations that does the same thing, one changes the value of v during runtime and one does not.

foo.cpp
1
2
3
4
5
6
7
#include "foo.hpp"

int bar(int v)
{
v += 3;
return v;
}
foo.cpp
1
2
3
4
5
6
7
8
#include "foo.hpp"

int bar(int const v)
{
int w = v;
w += 3;
return w;
}

The first implementation is slightly more efficient because the it does not have an additional local variable compared to the second one, but the second implementation is also valid.

If the interface is the second one, the implementation can only be the second one, and in some situations, such as the one that I mentioned above, the interface prevents us from creating more efficient implementations.

Example

Taken together, we picked the first interface and the second implementation as the library and created an additional main executable as a full example and compile.

foo.hpp
1
int bar(int v);
foo.cpp
1
2
3
4
5
6
7
8
#include "foo.hpp"

int bar(int const v)
{
int w = v;
w += 3;
return w;
}
main.cpp
1
2
3
4
5
6
7
8
9
#include <iostream>
#include "foo.hpp"

int main()
{
int v{5};
int w{bar(v)};
std::cout << w << std::endl;
}
1
2
3
4
5
6
$ g++ -c -o foo.o foo.cpp -std=c++11
$ g++ -shared -o libfoo.so foo.o
$ g++ main.cpp -o main -L. -lfoo -std=c++11
$ export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH
$ ./main
8

Conclusions

The best practice for const and pass-by-value is not having const in the interface and adding const if necessary in the implementation.

Author

Lei Mao

Posted on

04-03-2022

Updated on

04-03-2022

Licensed under


Comments