微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决为常量引用和右值引用编写重载

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

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

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

以这个玩具为例:

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::movestd::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!他们确实有回报:)

There's a beautiful library that leverages template metaprogramming and SFINAE heavily and successfully,但这确实是题外话:D

,

一个简单的解决方案是:

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

如果调用者传递一个左值,那么就会有一个副本(到参数中)和一个移动(到向量中)。如果调用者传递一个右值,则有两次移动(一次进入参数,另一次进入向量)。由于额外的移动(通过缺乏间接性稍微补偿),与两个重载相比,这可能稍微不太理想,但在移动便宜的情况下,这通常是一个不错的折衷方案。

Enlico's answer 中深入探讨了std::forward 模板的另一种解决方案。

如果您没有模板并且移动的潜在成本太高,那么您只需要满足于具有两个重载的一些额外样板即可。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。