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.
Questo è come dovrebbe essere. Quando chiami un'API, dovresti sapere cosa stai chiamando e perché. Dovresti anche sapere cosa fa la funzione e quali sono gli effetti collaterali (e anche le precondizioni e le postcondizioni).
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);)?
Suppongo che "farlo in questo modo" significhi "passare per riferimento".
Mi sembra che tu abbia reso l'associazione "ricevuta da pointer = may be changed". Questo non è corretto.
Ecco due scenari in cui il passaggio per riferimento / puntatore è preferibile rispetto all'alternativa:
-
operatori:
class EvenInteger { int value; public: /*...*/ }; // 0, 2, 4, 6, ...
EvenInteger operator +(const EvenInteger& x, const EvenInteger&y);
Qui, passando per riferimento si disattiva l'operatore per i valori temporanei, passando per puntatore si otterrebbe un codice client come questo:
EvenInteger a{0}, b{122};
auto c = &a + &b; // addition by address imposed if operator received pointers
-
istanza dell'osservatore (naturalmente passare 'this' come argomento):
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}; }
};
L'implementazione di begin
, riceve naturalmente un puntatore this
, senza implicare che sia in alcun modo un valore di ritorno. Scrivere l'iteratore per ricevere un riferimento impone il costrutto *this
sul codice client.
Di norma, l'interfaccia di un'API dovrebbe dirti questo:
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
Come puoi vedere, dovresti (sempre) fare affidamento sulla documentazione dell'API. Se ti affidi a una firma di funzione, può solo dirti quando non altera i suoi parametri ricevuti, e lo vedi attraverso const
, non attraverso il tipo di parametro (indirizzo / riferimento / etc ).
Ciò implica anche che devi sempre scrivere la documentazione per le tue API.