Probably tangential to your question, but I often take another direction to understand what happens when you call a function in C++.
The difference between
void foo(Bar bar); // [1]
void foo(Bar& bar); // [2]
void foo(Bar* bar); // [3]
is that the body in [1]
will receive a copy of the original bar (we call this by value, but I prefer to think of it as my own copy).
The body of [2]
will be working with the exact same bar
object; no copies. Whether we can modify that bar
object depends on whether the argument was Bar& bar
(as illustrated) or const Bar& bar
. Notice that in a well-formed program,[2]
will always receive an object (no null
references; let's leave dangling references aside).
The body of [3]
will receive a copy of the pointer to the original bar
. Whether or not I can modify the pointer and/or the object being pointed depends on whether the argument was const Bar* bar
, const Bar* const bar
, Bar* const bar
, or Bar* bar
(yes, really). The pointer may or may not be null
.
The reason why I make this mental distinction is because a copy of the object may or may not have reference semantics. For example: a copy of an instance of this class:
struct Foo {
std::shared_ptr<FooImpl> m_pimpl;
};
would, by default, have the same "contents" as the original one (a new shared pointer pointing to the same FooImpl
pointer). This, of course, depends on how did the programmer design the class.
For that reason I prefer to think of [1]
as "takes a copy of bar", and if I need to know whether such copy will be what I want and what I need I go and study the class directly to understand what does that class in particular means by copy.