为常量引用和右值引用编写重载



最近,我发现自己经常遇到一个函数将某个对象作为参数的情况。函数必须复制该对象。

然而,该函数的参数也可能经常是临时的,因此我也想提供该函数的重载,该重载采用右值引用而不是常量引用。

两个重载的不同之处在于,它们具有不同类型的引用作为参数类型。除此之外,它们在功能上是等效的。

例如,考虑这个玩具示例:

void foo(const MyObject &obj) {
globalVec.push_back(obj); // Makes copy
}
void foo(MyObject &&obj) {
globalVec.push_back(std::move(obj)); // Moves
}

现在我想知道是否有一种方法可以避免这种代码重复,例如用另一个函数来实现一个函数。

例如,我正在考虑在移动方面实现复制版本,如下所示:

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::movestd::forward是如何工作的,以及它们的相似之处和差异是解决您疑虑的关键,所以我建议您阅读我对std::movestd::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_asserts来证明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>>,
"nn*****nNot an int, aaargn*****nn");
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!它们确实有回报:(

有一个漂亮的库,它成功地大量利用了模板元编程和SFINAE,但这真的偏离了主题:D

一个简单的解决方案是:

void foo(MyObject obj) {
globalVec.push_back(std::move(obj));
}

若调用者传递了一个左值,那个么就有一个拷贝(到参数中(和一个移动(到向量中(。若调用者传递了一个右值,那个么有两个移动(一个移动到参数,另一个移动向量(。与两次过载相比,由于额外的移动(由于缺乏间接性而略有补偿(,这可能会稍微不那么优化,但在移动成本较低的情况下,这通常是一个不错的折衷方案。

模板的另一个解决方案是Enlico的答案中深入探讨的std::forward

如果你没有模板,而且搬家的潜在成本太高,那么你只需要满足于两次过载的额外样板。

最新更新