为 const 引用和右值引用编写重载
Write overloads for const reference and rvalue reference
最近我发现自己经常遇到一个函数以某个对象作为参数的情况。该函数必须复制该对象。
然而,该函数的参数也可能经常是临时的,因此我还想提供该函数的重载,它采用右值引用而不是 const 引用。
两种重载的区别仅在于它们具有不同类型的引用作为参数类型。除此之外,它们在功能上是等效的。
例如考虑这个玩具示例:
void foo(const MyObject &obj) {
globalVec.push_back(obj); // Makes copy
}
void foo(MyObject &&obj) {
globalVec.push_back(std::move(obj)); // Moves
}
现在我想知道是否有办法避免这种代码重复,例如根据另一个功能实现一个功能。
例如,我正在考虑按照这样的 move-one 来实现复制版本:
void foo(const MyObject &obj) {
MyObj copy = obj;
foo(std::move(copy));
}
void foo(MyObject &&obj) {
globalVec.push_back(std::move(obj)); // Moves
}
然而,这似乎仍然不理想,因为现在在调用 const ref 重载时发生了复制和移动操作,而不是之前需要的单个复制操作。
此外,如果对象不提供移动构造函数,那么这将有效地复制对象两次 (afaik),这违背了首先提供这些重载的全部目的(尽可能避免复制)。
我敢肯定有人可以使用宏和预处理器一起破解某些东西,但我非常想避免在这方面涉及预处理器(出于可读性目的)。
因此我的问题是:是否有可能实现我想要的(有效地只实现一次功能然后根据第一个重载实现第二个重载)?
如果可能的话,我想避免使用模板。
我的看法是(真正)理解std::move
和std::forward
是如何工作的,以及它们的相同点和不同点是解决你疑惑的关键,所以我建议你阅读我对 What's the difference between std::move
and std::forward
的回答,我在其中对两者给出了很好的解释。
在
void foo(MyObject &&obj) {
globalVec.push_back(obj); // Moves (no, it doesn't!)
}
没有动静。 obj
是一个变量的名称,将要调用的 push_back
的重载不是会从其参数中窃取资源的重载。
你必须写
void foo(MyObject&& obj) {
globalVec.push_back(std::move(obj)); // Moves
}
如果你想让移动成为可能,因为 std::move(obj)
说 看,我知道这个 obj
这里是一个局部变量,但我向你保证我不会以后不需要它,所以你可以把它当作一个临时的:如果你需要就偷走它的胆量。
关于您在
中看到的重复代码
void foo(const MyObject &obj) {
globalVec.push_back(obj); // Makes copy
}
void foo(MyObject&& /*rvalue reference -> std::move it */ obj) {
globalVec.push_back(std::move(obj)); // Moves (corrected)
}
让你避免它的是 std::forward
,你可以像这样使用它:
template<typename T>
void foo(T&& /* universal/forwarding reference -> std::forward it */ obj) {
globalVec.push_back(std::forward<T>(obj)); // moves conditionally
}
关于模板的错误消息,请注意有一些方法可以使事情变得更容易。例如,您可以在函数的开头使用 static_assert
来强制 T
是特定类型。这肯定会使错误更容易理解。例如:
#include <type_traits>
#include <vector>
std::vector<int> globalVec{1,2,3};
template<typename T>
void foo(T&& obj) {
static_assert(std::is_same_v<int, std::decay_t<T>>,
"\n\n*****\nNot an int, aaarg\n*****\n\n");
globalVec.push_back(std::forward<T>(obj));
}
int main() {
int x;
foo(x);
foo(3);
foo('c'); // errors at compile time with nice message
}
然后是 SFINAE,它更难,我想超出了这个问答的范围。
我的建议
不要害怕模板和 SFINAE!他们确实得到了回报:)
一个简单的解决方案是:
void foo(MyObject obj) {
globalVec.push_back(std::move(obj));
}
如果调用者传递左值,则有一个副本(到参数中)和一个移动(到向量中)。如果调用者传递一个右值,则有两个移动(一个进入参数,另一个进入向量)。由于额外的移动(由于缺乏间接性而略有补偿),与两个重载相比,这可能稍微不太理想,但在移动便宜的情况下,这通常是一个不错的折衷方案。
另一种模板解决方案是 std::forward
在 中深入探讨的。
如果您没有模板并且移动的潜在成本太高,那么您只需要满足于具有两个重载的一些额外样板。
最近我发现自己经常遇到一个函数以某个对象作为参数的情况。该函数必须复制该对象。
然而,该函数的参数也可能经常是临时的,因此我还想提供该函数的重载,它采用右值引用而不是 const 引用。
两种重载的区别仅在于它们具有不同类型的引用作为参数类型。除此之外,它们在功能上是等效的。
例如考虑这个玩具示例:
void foo(const MyObject &obj) {
globalVec.push_back(obj); // Makes copy
}
void foo(MyObject &&obj) {
globalVec.push_back(std::move(obj)); // Moves
}
现在我想知道是否有办法避免这种代码重复,例如根据另一个功能实现一个功能。
例如,我正在考虑按照这样的 move-one 来实现复制版本:
void foo(const MyObject &obj) {
MyObj copy = obj;
foo(std::move(copy));
}
void foo(MyObject &&obj) {
globalVec.push_back(std::move(obj)); // Moves
}
然而,这似乎仍然不理想,因为现在在调用 const ref 重载时发生了复制和移动操作,而不是之前需要的单个复制操作。
此外,如果对象不提供移动构造函数,那么这将有效地复制对象两次 (afaik),这违背了首先提供这些重载的全部目的(尽可能避免复制)。
我敢肯定有人可以使用宏和预处理器一起破解某些东西,但我非常想避免在这方面涉及预处理器(出于可读性目的)。
因此我的问题是:是否有可能实现我想要的(有效地只实现一次功能然后根据第一个重载实现第二个重载)?
如果可能的话,我想避免使用模板。
我的看法是(真正)理解std::move
和std::forward
是如何工作的,以及它们的相同点和不同点是解决你疑惑的关键,所以我建议你阅读我对 What's the difference between std::move
and std::forward
的回答,我在其中对两者给出了很好的解释。
在
void foo(MyObject &&obj) {
globalVec.push_back(obj); // Moves (no, it doesn't!)
}
没有动静。 obj
是一个变量的名称,将要调用的 push_back
的重载不是会从其参数中窃取资源的重载。
你必须写
void foo(MyObject&& obj) {
globalVec.push_back(std::move(obj)); // Moves
}
如果你想让移动成为可能,因为 std::move(obj)
说 看,我知道这个 obj
这里是一个局部变量,但我向你保证我不会以后不需要它,所以你可以把它当作一个临时的:如果你需要就偷走它的胆量。
关于您在
中看到的重复代码void foo(const MyObject &obj) {
globalVec.push_back(obj); // Makes copy
}
void foo(MyObject&& /*rvalue reference -> std::move it */ obj) {
globalVec.push_back(std::move(obj)); // Moves (corrected)
}
让你避免它的是 std::forward
,你可以像这样使用它:
template<typename T>
void foo(T&& /* universal/forwarding reference -> std::forward it */ obj) {
globalVec.push_back(std::forward<T>(obj)); // moves conditionally
}
关于模板的错误消息,请注意有一些方法可以使事情变得更容易。例如,您可以在函数的开头使用 static_assert
来强制 T
是特定类型。这肯定会使错误更容易理解。例如:
#include <type_traits>
#include <vector>
std::vector<int> globalVec{1,2,3};
template<typename T>
void foo(T&& obj) {
static_assert(std::is_same_v<int, std::decay_t<T>>,
"\n\n*****\nNot an int, aaarg\n*****\n\n");
globalVec.push_back(std::forward<T>(obj));
}
int main() {
int x;
foo(x);
foo(3);
foo('c'); // errors at compile time with nice message
}
然后是 SFINAE,它更难,我想超出了这个问答的范围。
我的建议
不要害怕模板和 SFINAE!他们确实得到了回报:)
一个简单的解决方案是:
void foo(MyObject obj) {
globalVec.push_back(std::move(obj));
}
如果调用者传递左值,则有一个副本(到参数中)和一个移动(到向量中)。如果调用者传递一个右值,则有两个移动(一个进入参数,另一个进入向量)。由于额外的移动(由于缺乏间接性而略有补偿),与两个重载相比,这可能稍微不太理想,但在移动便宜的情况下,这通常是一个不错的折衷方案。
另一种模板解决方案是 std::forward
在
如果您没有模板并且移动的潜在成本太高,那么您只需要满足于具有两个重载的一些额外样板。