Übergabeparameter und Rückgabewert haben nichts miteinander zu tun, die Zusammenhänge ergeben sich einfach aus der Funktionsweise der Funktion.
Es gibt einige Faustregeln für die Übergabe von Parametern:
Übergabe von Parametern, die nicht verändert werden, in der Funktion also nur gelesen werden:
Wertetyp (bool, char, int, long, double, etc.) werden per value übergeben
"billig" zu kopierende Datentypen (alles, was größenmäßig in ein CPU-Register oder Cacheline passt) werden per value übergeben
alles andere per const reference
double multiply( double f1, double f2 )
{
return f1 * f2;
}
struct simple_struct
{
double member = 0;
}
void foo( simple_struct s )
{
// simple_struct ist billig zu kopieren
}
struct complex_struct
{
std::string name;
std::vector<std::string> zeugs;
};
void bar( complex_struct const& cs )
{
// complex_struct ist "teuer" zu kopieren, also per const reference übergeben
...
}
void baz( std::string const& s )
{
// std::string ist "teuer" zu kopieren, also per const reference übergeben
}
Übergabe von Parametern, die in der Funktion verändert werden
Übergabe als reference
bool some_func( double f1, double f2, double& result )
{
// f1 und f2 sind billig zu kopieren, Übergabe per value. In result wird das Ergebnis geschrieben, also muss
// result als reference übergeben werden.
if( some_condition() )
{
result = f1 * f2;
return true;
}
return false;
}
Übergabe von optionalen Parametern, die nicht verändert werden:
Übergabe als const pointer
void foo( some_type const* param )
{
if( param )
{
param->do_something();
}
}
Übergabe von optionalen Parametern, die verändert werden können:
Übergabe als pointer
void foo( some_type const* param )
{
if( param )
{
param->result = calculate_something();
param->data = calculate_something_different();
}
}
Optimierungen, die durch move Semantik möglich sind, lasse ich hier mal außen vor.