7 ms·
Or, don't use out-parameters unless you really have to. If a function returns a value, have it return that value, instead of using an out-parameter. Passing in
by Karellen 5y ago
Or, don't use out-parameters unless you really have to.
If a function returns a value, have it return that value, instead of using an out-parameter. Passing in-parameters by const reference rather than by value is a nice win because the semantics of the function remain clear. Input parameters still look like input parameters, you're just preventing unnecessary copies where it's easy to do so. Returning a value as an out-parameter rather than just returning it is a performance hack that prevents a copy, but at the expense of muddying the semantics of the function.
As the prophet says: we should forget about small efficiencies, say about 97% of the time. Write code that clearly expresses what it does. Return your return values. If you run a profiler over your code and you notice that some returns are causing a performance problem - sure, at that point, add an overload that takes an out-parameter and use it at the frequenty-executed hot spot.
- thinkharderdev 5y agoI still don't really grok move semantics in C++ but is there a difference between assigning output to an out parameter and returning a value using std::move? Conceptually they seem the same to me but from what little I understand std::move doesn't quite do what I think it does.
- jcelerier 5y agoYou must not use std::move when returning a value, just let RVO do its job. The compiler will make it so that the variable you are returning from your function will be constructed in-place at the variable you are assigning the function call to. However, if you are compiling at -O0 without any optimizations this may not happen.
- kaashif 5y agoSince C++17, compilers are now required to elide copies and use RVO: https://en.cppreference.com/w/cpp/language/copy_elision https://en.cppreference.com/w/cpp/language/copy_elision There are probably exceptions and loopholes just like with literally everything in C++, but there's this line: > Return value optimization is mandatory and no longer considered as copy elision; see above.
- jcelerier 5y agoThat's only in the unnamed case. Here's a simple case than GCC does not optimize even in -std=c++20 (and even at -O3 LOL): https://gcc.godbolt.org/z/jKPr98Mhx https://gcc.godbolt.org/z/jKPr98Mhx
- kaashif 5y agoIsn't it kind of confusing that RVO is mandatory, but it turns out RVO only refers to unnamed RVO and not NRVO? I would've thought that since NRVO is a type of RVO... Thanks for the clarification. Clang does do NRVO in that case but I guess it's obviously not guaranteed by the standard.
- jcelerier 5y agoYes, I believe that explicitly qualifying unnamed and named rvo should be the norm and would clarify a lot of confusion on these topics. I guess it makes sense that an optimization that GCC is not able to do at all should not be part of the standard ; I'm pretty sure it should be possible to write cases that clang wouldn't be able to optimize either.
- deleted 5y ago[deleted]
- cyber_kinetist 5y agoOof, that’s a bit painful. I thought you could rely on NRVO for recent versions of gcc and clang, but feel I should be a lot more careful after seeing that example.
- jcelerier 5y agoFrom what I could see through hundreds of godbolt snippets, if you declare the variable you'll return at the very top of the function before any control flow, NRVO will pretty much always happen.
- bergesenha 5y agoSince C++17 I believe this is guaranteed.
- jcranberry 5y agoThat only applies when you're returning a prvalue. NRVO isn't guaranteed: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58055 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58055
- Denvercoder9 5y agoThe most common usage of out-parameters is when you want to return more than one thing from a function, in which case they can't be trivially converted to return values.
- ogogmad 5y agoSurely in C++ you can return a tuple. It's only in C you can't.
- huhtenberg 5y agoYou can return a struct in C, but that's as unconventional as it gets.
- Denvercoder9 5y agoSure, but that doesn't always help readability/clarity. Compare this: SomeType result; if (!do_something(&result)) return; process(result); with this: std::tuple<bool, SomeType> ret = do_something(); if (!std::get<0>(ret)) return; process(std::get<1>(ret)); You could improve it a bit by using `auto` or returning a struct with named members, but quite often I still find it less readable. Especially in situations that are more complex than this example, where e.g. you fallback to another function to fetch `result` if `do_something` failed.
- kaashif 5y agoIf there are only two status codes (success/failure) then it seems like optional would be more appropriate than a tuple of bool and SomeType. const std::optional<SomeType> result = do_something(); if (!result.has_value()) { return; } process(result.value()); This is nicer than the output parameter in my opinion, because it may be unclear what the meaning of a default constructed SomeType is. Default values are sometimes dangerous if the default is a real, valid input - errors and invalid states can end up being passed through. C++ doesn't have great support for sum types, so this is all always going to be pretty non-ergonomic, and the compiler doesn't always warn if you forget to do things. I might even say that the main advantage of using sum types is gone in C++ - you actually can forget to check has_value and call operator* on an empty optional. It would be so easy for someone to make a mistake and write: process(*do_something()); And invoke undefined behaviour. Whereas processing a default-constructed SomeType is at least not undefined behaviour (depending on what SomeType is, and what process does...). This whole thing is a mess, I hate C++, thinking about UB all the time...
- erwincoumans 5y agoJust like you want to avoid a copy of the input arguments, you may also want to avoid a copy of the output/return value.