Skip to content

Commit 2005c54

Browse files
committed
Adding Item 41 of Effective Modern C++
1 parent d710a78 commit 2005c54

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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

Comments
 (0)