4 ms·
"in places like Google". Are you speaking from experience? As a former googler, I can say with certainty that the guidelines for passing any non primitive is
by elromulous 3y ago
"in places like Google".
Are you speaking from experience?
As a former googler, I can say with certainty that the guidelines for passing any non primitive is pointer or ref.
string_view might be the only exception I can think of.
- e____g 3y agoI saw std::function and std::string (e.g. TotW 127, https://abseil.io/tips/117 https://abseil.io/tips/117) being passed by value a lot in newer google3 code. Both are larger than 16 bytes.
- jeffbee 3y agoGoogle builds non-PIE, non-PIC, static, profile-guided, link-time-optimized, and post-link-optimized binaries and probably DGAF about calling conventions.
- pclmulqdq 3y agoI have seen the assembly output of Google code, and I will say that my previous comment still stands.
- vitus 3y agoPassing std::function by value is almost definitely wrong these days with absl::AnyInvocable (if you need to store the type) and absl::FunctionRef (if you don't). Rough analogues in the standard are std::move_only_function (C++23) and std::function_ref (C++26). std::string in the case you cited is only really relevant if you std::move() into it and you would otherwise incur a string copy. Yes, it's bigger than 16 bytes (24 bytes), but that pales in comparison to the alternative. (Taking std::string&& would eliminate the possibility of misuse / accidental copies, but that pattern is generally discouraged by Google's style guide for various reasons.) Also, just because you see a certain pattern an awful lot even at Google doesn't mean that it's best practice -- there are plenty of instances of protobufs being passed by value...
- eco 3y agoThis is done so you can use std::move to take ownership of the allocated memory in these objects rather than do a new allocation. Passing by value rather than rvalue reference let's your function be more flexible at the call site. You can pass an rvalue/move or just make a copy at the call site which means the caller (who actually knows if a copy or a move is more appropriate) gets to control how the memory gets allocated. An unnecessary memory allocation is much more of a performance hit than suboptimal calling convention.
- jstimpfle 3y agoIn that case, the optimal interface should take std::string&& no? But it's awkward.
- andersa 3y agoWouldn't this be very annoying to work with, because now you have to explicitly move or copy the string whenever you want to construct one of these objects?
- nitnelave 3y agoIt's kinda what Rust forces you to do, except that std::move is implied. Anything taken by value is equivalent to taking by && unless the type is explicitly marked as Copy (i.e. it can be trivially copied and the copies are implicit). But yeah, in a c++ codebase, good modern practices are often verbose and clunky.
- ismailmaj 3y agoThe function could accept an universal reference instead of an rvalue reference, this avoids the dance the caller has to do to pass a copy. IMO it's hard to beat pass by value considering both performance and cognitive load.
- mort96 3y agoYeah, making it accept a universal reference would fix it... ...but that requires the argument to be a type from a template D: so you'd have to write: template<typename String = std::string> void dosomething(String &&str) ...and that's not quite right either, since you'd want it to be either an rvalue reference or a const lvalue reference
- leni536 3y agonot trivially copyable types are never passed in registers regardless of size
- jtasdlfj234 3y agoPlease prove me wrong on these points. My current belief and understanding is that: 1. `foo(T)` indicates polymorphic over both `T::T(const T&)` and `T::T(T&&)`. This gives you benefits of both pass-by-move (using `std::move` as needed), or copy. 2. Usage of `foo(T&&)` signals code-smell or an anti-pattern as `foo(T)` should be used instead unless it is perfecting forwarding / universal reference `template <typename T> foo(T&&)`.
- pclmulqdq 3y agoYes. A lot of 24-byte structs at Google are passed by value.
- japanman185 3y agoIndeed they are. I read about it from hackernews.
- ductsurprise 3y ago0o...
- pclmulqdq 3y agoI learned about it when I was at Google working on performance.
- dataflow 3y ago> string_view might be the only exception I can think of. What about std::span? std::optional? etc.
- nemetroid 3y agostd::span should apply, but std::optional is at least as large as the type it wraps.
- fyrn_ 3y agoAlmost always 8 + the size of the type. Since it's a one byte book followed by 7 padding bytes, followed by the type itself.
- vitus 3y agoView types like span, string_view, FunctionRef: absolutely pass by value. They're designed to be cheap to copy, and they provide type erasure which can make your API more flexible. You also usually want to pass smart pointers (unique_ptr, shared_ptr) by value to avoid breaking your ownership model, or pass by raw pointer or reference to the held data. The obvious case that comes to mind where you'd want to write const unique_ptr<T>& is when iterating over a vector<unique_ptr<T>>. Otherwise, pass ptr.get() for an optional param, or *ptr for a required param where you've already checked ptr != nullptr. Wrapper types like optional, variant, StatusOr? Usually pass by reference, unless the pointed-to type is small (<= 8 bytes). One common pattern where I see wide structs being passed by value is the use of option structs (https://abseil.io/tips/173 https://abseil.io/tips/173) that are usually constructed via designated initializers. There is some marginal benefit to being able to std::move() out individual fields but it's not really a big deal either way, as said computation is usually only done once on program initialization.
- deleted 3y ago[deleted]
- medler 3y agoPassing larger objects by value is allowed or even encouraged if it lets you avoid a copy (e.g. if you’re about to move the object)