If I'm writing code on the client side, I don't know which of the variables I pass to a function might be changed and which I can expect to remain the same without an explicit knowledge of the parent function declaration.
This is as it should be. When you call an API, you should know what you are calling and why. You should also know what the function does and what it's side effects are (and preconditions and postconditions as well).
What is the advantage of doing it this way versus explicitly expecting a pointer (e.g., int func(int *param), which would then be called func(¶m);)?
I will assume that "doing it this way" means "passing by reference".
It seems to me you make the association "received by pointer = may be changed". This is incorrect.
Here are two scenarios when passing by reference/pointer is prefered over the alternative:
operators:
class EvenInteger { int value; public: /*...*/ }; // 0, 2, 4, 6, ...
EvenInteger operator +(const EvenInteger& x, const EvenInteger&y);
Here, passing by reference would disble the operator for temporary values, passing by pointer would result in client code like this:
EvenInteger a{0}, b{122};
auto c = &a + &b; // addition by address imposed if operator received pointers
instance observer (naturally pass 'this' as an argument):
class Collection // collection/sequence of arbitrary objects of type Obj
{
public:
class InterestingIterator {
Collection* parent;
InterestingIterator(Collection* c); // accessible by Collection
public:
// ...
}
InterestingIterator begin() { return InterestingIterator{this}; }
};
The implementation of begin
, naturally receives a this
pointer, without implying it is in any way a return value. Writing the iterator to receive a reference imposes the *this
construct on client code.
As a rule, the interface of an API should tell you this:
void f(int x); // makes own copy of x, doesn't return/alter value
void f(int& x); // _may_ alter value (check docs)
void f(int* x); // _may_ alter value, or populate it with a return (check docs)
void f(const int& x); // observer of x
void f(int* const x); // _may_ alter value pointed by x, but not the address
void f(const int* x); // _may_ alter address, but not the pointed value
void f(const int* const x); // observer of value and address
As you can see, you should (always) rely on an API's documentation. If you rely on a function signature, it can only tell you when it doesn't alter it's received parameters, and you see that through const
, not through the parameter type (address/reference/etc).
This also implies that you should always write documentation for your APIs.