As always when someone brings the argument that one thing is faster than the other, did you take timings? In fully optimized code, in every language and every compiler you plan to use? Without that, any argument based on performance is moot.
I’ll come back to the performance question in a second, just let me address what I think is more important first: There are good reasons to pass function parameters by reference, of course. The primary one I can think of right now is that the parameter is actually input and output, i.e., the function is supposed to operate on the existing data. To me, that is what a function signature taking a non-const reference indicates. If such a function then ignores what is already in that object (or, even worse, clearly expects to only ever get a default-constructed one), that interface is confusing.
Now, to come back to performance. I cannot speak for C# or Java (though I believe returning an object in Java would not cause a copy in the first place, just passing around a reference), and in C, you do not have references but might need to resort to passing pointers around (and then, I do agree that passing in a pointer to uninitialized memory is ok). But in C++, compilers have for a long time done return value optimization, RVO, which basically just means that in most calls like A a = f(b);
, the copy constructor is bypassed and f
will create the object directly in the right place. In C++11, we even got move semantics to make this explicit and use it in more places.
Should you just return an A*
instead? Only if you really long for the old days of manual memory management. At the very least, return an std::shared_ptr<A>
or an std::unique_ptr<A>
.
Now, with multiple outputs, you get additional complications, of course. The first thing to do is if your design is actually proper: Each function should have a single responsibility, and usually, that means returning a single value as well. But there are of course exceptions to this; e.g., a partitioning function will have to return two or more containers. In that situation, you may find that the code is easier to read with non-const reference arguments; or, you may find that returning a tuple is the way to go.
I urge you to write your code both ways, and come back the next day or after a weekend and look at the two versions again. Then, decide what is easier to read. In the end, that is the primary criterion for good code. For those few places where you can see a performance difference from an end-user workflow, that is an additional factor to consider, but only in very rare cases should it ever take precedence over readable code – and with a little more effort, you can usually get both to work anyway.