|
| 1 | +# Consider Pass-by-Value Instead of Overloading |
| 2 | + |
| 3 | +Given C++11 move semantics, it is often a good idea to allow for moving objects |
| 4 | +into your types. For example, we could have a class `Person` with a name, and |
| 5 | +corresponding access function to that name. Because we are very smart and value |
| 6 | +performance, we add overloads for lvalue and rvalue strings to set the person's |
| 7 | +name: |
| 8 | + |
| 9 | +```C++ |
| 10 | +class Person { |
| 11 | +public: |
| 12 | + /* ... */ |
| 13 | + |
| 14 | + void name(const std::string& name) { |
| 15 | + _name = name; |
| 16 | + } |
| 17 | + |
| 18 | + void name(std::string&& name) { |
| 19 | + _name = std::move(name); |
| 20 | + } |
| 21 | +private: |
| 22 | + std::string _name; |
| 23 | +}; |
| 24 | +``` |
| 25 | +
|
| 26 | +However, one disadvantage of this ist that we now have to maintain *two* |
| 27 | +functions instead of one. This also bloats our object code. An alternative would |
| 28 | +be to use universal reference overloads. This would also allow for more |
| 29 | +efficient copying/moving of `const char*`, for example. However, using universal |
| 30 | +references itself bears problems (e.g. when introducing new non-template |
| 31 | +overloads that) and it does not solve the code-bloat problem, because the |
| 32 | +respective overloads (and in this case probably even more) will still be |
| 33 | +instantiated. |
| 34 | +
|
| 35 | +Item 41 in Effective Modern C++ suggests a counter-intutive alternative to the |
| 36 | +above dilemma: passing by value: |
| 37 | +
|
| 38 | +```C++ |
| 39 | +class Person { |
| 40 | +public: |
| 41 | + void name(std::string name) { |
| 42 | + _name = std::move(name); |
| 43 | + } |
| 44 | +private: |
| 45 | + std::string _name; |
| 46 | +}; |
| 47 | +``` |
| 48 | + |
| 49 | +The advantage of this is that we only need to maintain one function. Also, this |
| 50 | +operation need not be as inefficient as it looks. Ultimately, it will incur |
| 51 | +exactly one more move operation relative to the operations that needed to be |
| 52 | +performed for the overloads previously. If an lvalue is passed, it will be |
| 53 | +copied into the parameter, and then moved into _name. If we had just used a |
| 54 | +`const&` overload, we had had only the copy, but not the move. For rvalues, we |
| 55 | +will incur exactly one more move operation, adding to the one we would have |
| 56 | +needed for the `&&` overload. |
| 57 | + |
| 58 | +However, this may still be a problematic overhead. The factors listed below make |
| 59 | +the idiom particularly ineffective for certain cases: |
| 60 | + |
| 61 | +* The class is not copyable at all. For example, `std::unqiue_ptr` objects can |
| 62 | + only be taken by `&&` anyway. Therefore, we had only needed one function in |
| 63 | + the first place and the pass-by-value idiom will only decrease performance |
| 64 | + |
| 65 | +* Move operations are not cheap on the object. Move operations are not |
| 66 | + implicitly more efficient than copies, but can sometimes (e.g. for |
| 67 | + `std::array`s) be just as expensive as a copy. In those cases, the additional |
| 68 | + move is something we most definitely cannot tolerate. |
| 69 | + |
| 70 | +* The object is not always copied into the destination. Let us consider a |
| 71 | + function that takes a string as above, but only copies it under certain |
| 72 | + conditions, such as that its size is less than some maximum. In that case, we |
| 73 | + will have incurred the cost of one copy (when an lvalue is passed), while the |
| 74 | + overloaded versions had cost nothing at all. |
| 75 | + |
| 76 | +* The class is a base class and slicing may occur. Taking a parameter by value |
| 77 | + is subject to the slicing problem, while taking a parameter by reference |
| 78 | + preserves its dynamic type. |
| 79 | + |
| 80 | +In general, this idiom will never be more efficient than standard |
| 81 | +methods. It can only be a good compromise between code brevity and |
| 82 | +style on the one hand, and performance on the other hand. |
0 commit comments