.CPP files under the hood with pitfalls!
2. Extended talk about references and pointers
Introduction
This topic is continuing my previous article. I am going to make additional speech about reference and a little bit about pointers, because I have left behind some issues. Also my previous article wasn't small, however it didn't fit all information.
Now I will talk about some missed explanation examples, reference as return value or argument for function\method, overriding of operator & and covariation in C++.

Extra explanation of pointers vs. references examples
or this song is nice, let's play it again
If you remember, in the previous article I said that reference point to a single object and pointer can point to whole array of objects (in common sense, including primitive types). But I didn't say that reference can't point to another reference contrary to pointers.
This capability of pointers is very useful. For example I think everybody knows that matrix in mathematics and pointers suit well on low level. See:
|
const std::size_t length = 3 // Creates matrix 3x3 which a(ij) element is sum of its indexes. // creates array of pointers and assigns it to pointer to pointer. int **array = new int* [length]; for (std::size_t i = 0; i < length; ++i) { // creates pointer to array of integers. array[i] = new int [length]; for (std::size_t j = 0; j < length; ++j) { // assigns each item of the array sum of its indexes. array[i][j] = i + j; } } |
So in code above we created matrix with dimensions 3x3, which is represented as pointer to array of pointers to integer arrays. To access each item of the matrix we can use operator []. See how we can print out the matrix:
|
// Prints matrix. for (std::size_t i = 0; i < length; ++i) { for (std::size_t j = 0; j < length; ++j) { std::cout << " " << array[i][j]; } std::cout << std::endl; } |
And to prevent leaks you have to release such matrix correctly:
|
// Free resources. for (std::size_t i = 0; i < length; ++i) { delete []array[i]; } delete []array; |
I use delete[] operator every time because we used operator new[] for allocation. Remember the rule: what was allocated with new[] has to be release with delete[].
As for references you can't make a reference to reference:
|
int value = 3; int &ref = value; // oups, compile error: cannot bind int lvalue to int&& int &&ref2 = ref; |
Actually there is C++11 syntax for r-value reference. I won't describe this now because this is a subject for another article, but if you want, you can look it up on the Internet. Just remember your compiler won't compile it for the purpose you want, but actually I don't know such purposes. Even more - don't think about it. It is a taboo, just like trying to create an array of references:
|
// oups, compile error: declaration of refs as array of references int &refs[] = { 1, 2 }; |
One more difference between pointers and references is a result of applying “address of” operator (“&”). Reference has the same address as the object it points to, but pointer doesn't:
|
int value = 3; int &ref = value; int *ptr = &value; std::cout << "Value address: " << &value << std::endl; std::cout << "Reference address: " << &ref << std::endl; std::cout << "Pointer address: " << &ptr << std::endl; std::cout << "But: " << &*ptr << std::endl; std::cout << "And: " << ptr << std::endl; |
Output in console will be different every time, but for example for me it printed out:
|
Value address: 0x7fffddcecfbc Reference address: 0x7fffddcecfbc Pointer address: 0x7fffddcecfb0 But: 0x7fffddcecfbc And: 0x7fffddcecfbc |
Address of value and reference is the same, which doesn't hold for pointers as I said earlier. In the last two cases you see the same address as value address because via &*ptr and ptr we get direct access to address of an object.
Keep value alive
or dead man doesn't walk
As you probably know local variables are allocated on stack. What will happen if one returns reference to such variable from a function\method? It is a horrible story, because call side of the function\method gets reference to dead object because all variables on a stack are released when their scope ends. So the following code causes segmentation fault (or not, the behavior is undefined):
|
std::vector<int>& returnDeadReference() { // gcc provides warning here: reference to local variable values returned std::vector<int> values = {0}; return values; } ... std::vector<int>& ref = returnDeadReference(); // you still can read, but it will be garbage, it can print 17731680 e.g. std::cout << "Dead vector item: " << ref[0] << std::endl; ref.push_back(10); // probably segmentation fault. |
We still can read some values from std::vector<int>&, because reference has the object address and nobody cares whether it is alive or not. Compiler doesn't care too and puts call of std::vector<T>::operator [] which therefore performs as usually and reads something from the released memory. On std::vector<T>::push_back() we might get segmentation fault, because called function tries to work with internal resources which don't exist anymore. So there is a hidden issue which can't be found immediately if you don't know about such cases or don't worry about compile warnings. The last one is very bad, dude.
If you want to return something by reference, commonly it may take place with fields of class and with usage of const modifier, which is still not considered to be a good practice. On the other hand, one can safely return reference to statically allocated object, which is widely used to control time and order of creation of such objects.
This is what I wanted to say about reference as return value, lets proceed with reference as input arguments.
Imagine that you have such function:
|
void inputReference(int &value) { // Some actions. } |
If you use it like this:
|
int ten = 10; inputReference(ten); |
everything is good until you pass value 10 directly to function, in which case you'll get compile error:
|
// error: no matching function for call to inputReference(int) inputReference(10); |
Here 10 is r-value and as temporary object can be passed only by value, in our case compiler doesn't find function with such interface. If you change type argument from int& to int it will work, but there is a way to bind this r-value object. If you use const modifier for int& type it will compile and work:
|
void inputConstReference(const int &value) { // Some actions. } ... inputConstReference(10); // works! |
This way it keeps 10 temporary alive and you can work with such object through constant operations. In our case it is readable. So if you don't change referenced argument it is a good practice to pass it with const modifier.
You can use the similar trick for keeping r-value temporary alive by binding it to a local variable which is constant reference. See code:
|
// error: invalid initialization of non-const reference // of type std::vector<int>& from an rvalue of type std::vector<int> std::vector<int> &value = std::vector<int>(10, 0); // But this works. const std::vector<int> &value = std::vector<int> {0, 1, 2, 3, 4}; |
Here constant reference extends lifetime of temporary object until it goes from scope. So keep in mind that constant reference is your good friend.
Overriding operators and reference
or a little hack in the name of goodness
Everybody knows that you can override operators in C++. Operator &, obtaining address of class instance, can be overridden too. It can be necessary when some instance is wrapped by proxy class, but for some purpose instance of proxy class must return address of wrapped instance. The code looks like following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
class ProxyReference { public: ProxyReference() : field(10) { } const ProxyReference* operator&() const { return reinterpret_cast<const ProxyReference*>(&field); } const int& getField() const { return field; } private: int paddingField; int field; }; ... ProxyReference proxy; std::cout << "Address of class: " << &proxy << std::endl; std::cout << "Address of its field: " << &proxy.getField() << std::endl; |
For me it printed something like this:
|
Address of class: 0x7fff0cd1ccc4 Address of its field: 0x7fff0cd1ccc4 |
As you can see it has printed the same addresses as for instance of class and as for its field, which I used like wrapped instance. But what can you do if you want to know real address of proxy class?
Of course you can return it by special method:
|
class ProxyReference { public: ... const ProxyReference* getRealAddress() const { return this; } ... }; ... std::cout << "Real address of class: " << proxy.getRealAddress() << std::endl; |
It printed for me:
|
Real address of class: 0x7fff0cd1ccc0 |
This is the real address of proxy class, but it isn't cool to write such a method every time for every class. So there is std::addressof() helper function from <memory> header in blessed C++11. See:
|
// Printed for me: // Get real address in C++11: 0x7fff0cd1ccc0 std::cout << "Get real address in C++11: " << std::addressof(proxy) << std::endl; |
But it can't be suitable for all folks, because what to do if your compiler doesn't support C++11 or has only partial support? And how to do it cross-platform? reinterpret_cast<T> hurries to help! Check out such hack:
|
template<typename T> const T* getHackedAddress(const T& obj) { const unsigned char &ch = reinterpret_cast<const unsigned char&>(obj); return reinterpret_cast<const T*>(&ch); } // Printed for me: // Use reinterpret hack: 0x7fff0cd1ccc0 std::cout << "Use reinterpret hack: " << getHackedAddress(proxy) << std::endl; |
See? reinterpret_cast<T> helps us to cast some reference to constant reference of unsigned char and cast it back to pointer type. In such a way pointer points to real address of instance and template function provides it for every type.
Return type covariation
or partly covariance
First of all lets define covariance. It is support of subtyping for inherited classes. For example if we have such inherited classes:
|
class Human {}; class CProgrammer : public Human {}; |
Covariance for such classes must guarantee that std::vector<CProgrammer> is subtype of std::vector<Human>. Here covariance means that std::vector<T> saves inheritance. So just a second ago you have read what C++ doesn't support. Templates don't provide it. They are invariant, because it will destroy type control.
However good news - C++ supports covariant return types via pointers and references. It means that you can change return type when overriding virtual methods. In such case types are covariant. Here is an example of overriding with different return type through pointers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
class BaseCovariantPtr { public: virtual BaseCovariantPtr* getCovariant() { std::cout << "BaseCovariantPtr::getCovariant()" << std::endl; return this; } }; class OverCovariantPtr : public BaseCovariantPtr { public: virtual OverCovariantPtr* getCovariant() { std::cout << "OverCovariantPtr::getCovariant()" << std::endl; return this; } }; |
If you run such code:
|
BaseCovariantPtr *basePtr = new BaseCovariantPtr(); BaseCovariantPtr *overPtr = new OverCovariantPtr(); basePtr->getCovariant(); overPtr->getCovariant(); delete basePtr; delete overPtr; |
You will get in output:
|
BaseCovariantPtr::getCovariant() OverCovariantPtr::getCovariant() |
Same is true for references, but you can't do it with returning by value. It causes compile errors.
Such feature is used for implementing virtual constructor idiom and can be useful in other cases.
Conclusion
In this article I have written about some differences between references and pointers that were left out in my previous article. Also I have shown examples of keeping r-value alive by constant reference, underlined issue with returning reference to variable which is allocated on stack. As well I have described how to get address of class instance which has overloaded operator &. Finally we reviewed covariation of return type in C++. As usual you can find examples of code in my repository on Github.
Author:
Andrew Kramarenko
C++ Developer
Enjoyed this article?
you might want to subscribe for our newsletter to get more content like this: