C++ 通过右值引用而不是通过值获取接收器参数以强制接口的性能使用
在一次代码审查中,我的同事和我讨论了我正在编写的函数的接口。我们的代码库使用C++17,不使用异常(视频游戏)C++ 通过右值引用而不是通过值获取接收器参数以强制接口的性能使用,c++,C++,在一次代码审查中,我的同事和我讨论了我正在编写的函数的接口。我们的代码库使用C++17,不使用异常(视频游戏) 我声称,采用CUN参数的惯用C++方式是性能化的,同时也保持接口灵活,允许调用方通过拷贝或从所需的值中移动。按照惯用的方式,我指的是一个按值获取参数的函数,或者是一个const-lvalue和rvalue引用的重载集(这在左值情况下需要较少的移动,代价是一些代码重复) 调用方知道f已取得x的所有权。与 void g(T&& x); g可能拥有所有权,也可能没有所有权,
我声称,采用CUN参数的惯用C++方式是性能化的,同时也保持接口灵活,允许调用方通过拷贝或从所需的值中移动。按照惯用的方式,我指的是一个按值获取参数的函数,或者是一个const-lvalue和rvalue引用的重载集(这在左值情况下需要较少的移动,代价是一些代码重复)
调用方知道f
已取得x
的所有权。与
void g(T&& x);
g
可能拥有所有权,也可能没有所有权,这取决于f
的实现
有没有最好的办法?我是不是漏掉了一些参数?你基本上有这些构造函数选项:
class myclass {
public:
// #1 myclass(const std::string& s) : s(s) {}
// #2 myclass(std::string&& s) : s(std::move(s)) {}
// #3 myclass(std::string s) : s(std::move(s)) {}
// #4 template <typename T> myclass(T&& t) : s(std::forward<T>(t)) {}
std::string s;
};
以下是复制/移动构造函数的计数
| A | B | C | D | E |
------------------------|---|---|---|---|---|
1 Copy |<1>| 2 | 1 | 1 | 1 |
const l-value ref Move |<0>| 1 | 1 | 0 | 0 |
Other |<0>| 0 | 0 | 1 | 1 |
------------------------|---|-v-|-v-|-v-|-v-|
2 Copy | X |<1>|<0>| 0 |<0>|
r-value ref Move | X |<1>|<1>| 1 |<1>|
Other | X |<0>|<0>| 1 |<1>|
------------------------|---|---|---|---|---|
3 Copy | 1 | 1 | 0 | 0 |<0>|
by value Move | 1 | 2 | 2 | 1 |<1>|
Other | 0 | 0 | 0 | 1 |<1>|
------------------------|---|---|---|---|---|
4 Copy |<1>|<1>|<0>|<0>| X |
Forwarding ref Move |<0>|<1>|<1>|<0>| X |
Other |<0>|<0>|<0>|<1>| X |
--------------------------------------------/
| A | B | C | D | E|
------------------------|---|---|---|---|---|
1份| 2 | 1 | 1 | 1|
常数l值参考移动| | 1 | 1 | 0 | 0|
其他| 0 | 0 | 1 | 1|
------------------------|---|-v-|-v-|-v-|-v-|
2份| X | | 0||
r值参考移动| X | | | 1||
其他| X | | 1||
------------------------|---|---|---|---|---|
3份| 1 | 1 | 0 | 0||
按值移动| 1 | 2 | 2 | 1||
其他| 0 | 0 | 0 | 1||
------------------------|---|---|---|---|---|
4份复印件|
转发参考移动|
其他|
--------------------------------------------/
可能的配置:
仅限:处理所有案例,但复制用于临时#1
:(B/C/D/E将使用#1/#2
),因此除了就地施工外,效果最好#2
仅限:处理所有情况,但执行额外移动#3
仅限:处理大多数常规案例,获得最佳结果#4
:最佳结果(请注意,#1/#2/#4
)与非常量l值完全匹配)#4
:最佳结果#2/#4
- 仅限
禁止复制,但显式复制(B)比#2
/#1
多移动1步#2
- 转发引用(
)的效果最好#4
- By const ref(
)的复制性能最好,但其他复制性能较差#1
- 然后按值(
)是第二个“最差”,但只比最好的多了一步#3
- C++11之前只有
可用(大多数界面的默认设置也是如此)#1
- 只有
可能意味着没有所有权转让#1
- 仅移动(
)禁止隐式复制#2
- By Value
的目的是只编写一个重载作为良好的折衷#3
#1
/#2
、#2
和#3
:
- 对于仅移动类型:
/#1
是不相关的#2
立即下沉#3
有机会处理异常保证:如果抛出,则不需要使用对象#2
) 对于现有的代码库,我将保持一致性#2
#1
/#2
是最有效的,但允许不需要的复制。
-#3
很方便(而且只需一次额外移动),但允许不需要的复制,保证接收。
-#2
避免不需要的拷贝
现在,这主要是您想要保证和允许的:
- 最佳性能->
#1/#2
- 无(隐式)副本->
#2
- 即时/保证接收->
#3
- 与仅可移动类型的一致性 对于现有的代码库,我将保持一致性
void g(T&& x);
class myclass {
public:
// #1 myclass(const std::string& s) : s(s) {}
// #2 myclass(std::string&& s) : s(std::move(s)) {}
// #3 myclass(std::string s) : s(std::move(s)) {}
// #4 template <typename T> myclass(T&& t) : s(std::forward<T>(t)) {}
std::string s;
};
std::string s;
myclass A(s);
myclass B(std::string(s));
myclass C(std::move(s));
myclass D("temporary");
myclass E({5, '*'});
| A | B | C | D | E |
------------------------|---|---|---|---|---|
1 Copy |<1>| 2 | 1 | 1 | 1 |
const l-value ref Move |<0>| 1 | 1 | 0 | 0 |
Other |<0>| 0 | 0 | 1 | 1 |
------------------------|---|-v-|-v-|-v-|-v-|
2 Copy | X |<1>|<0>| 0 |<0>|
r-value ref Move | X |<1>|<1>| 1 |<1>|
Other | X |<0>|<0>| 1 |<1>|
------------------------|---|---|---|---|---|
3 Copy | 1 | 1 | 0 | 0 |<0>|
by value Move | 1 | 2 | 2 | 1 |<1>|
Other | 0 | 0 | 0 | 1 |<1>|
------------------------|---|---|---|---|---|
4 Copy |<1>|<1>|<0>|<0>| X |
Forwarding ref Move |<0>|<1>|<1>|<0>| X |
Other |<0>|<0>|<0>|<1>| X |
--------------------------------------------/