C++右值引用和移动语义

6
所以我昨天在YouTube上观看了有关C++-11右值引用和移动语义的视频。我认为我大致理解了概念,但今天当我与TA一起查看我的代码时,他问我为什么没有使用引用(例如std :: pair <HostName, IPAddress>& p )在下面的代码中。在这种情况下,我根本没有考虑过它,但当他问时,我记得视频说“在C++-11中,通常应该使用按值传递”。
我的问题是:在下面的代码中,std :: pair <HostName, IPAddress> p 是否像std :: pair <HostName, IPAddress>& p 一样更好?将使用移动语义吗?会有所不同吗?
IPAddress NameServer::lookup( const HostName& host ) const {
    auto it = std::find_if( vec.begin(), vec.end(),
                       [host] ( std::pair<HostName, IPAddress> p ) {
        return p.first == host;
    } );
    ...
}
1个回答

11
在这种情况下,你应该使用const引用进行传递。当你最终想要生成传递值的副本或移动时,按值传递是有意义的;如果你不想复制也不想移动,特别是如果你只想观察,那么你应该通过(const)引用进行传递。
在这里,你的lambda谓词不需要生成任何输入对的副本:因此,没有理由按值传递(也没有理由按值捕获)。
IPAddress NameServer::lookup( const HostName& host ) const {
    auto it = std::find_if( vec.begin(), vec.end(),
        [&host] ( std::pair<HostName, IPAddress> const& p ) {
    //   ^^^^^                                   ^^^^^^
        return p.first == host;
    } );
    ...
}

考虑一下这个例子(典型的C++03代码):
struct A
{
    A(string const& s) : _s(s) { }
private:
    string _s;
};

在C++11中,由于有移动语义,你可以直接通过传值来将s移动到成员变量中,而不是通过常量引用进行传递。
struct A
{
    A(string s) : _s(move(s)) { }
private:
    string _s;
};

这是有道理的,因为我们总是最终生成传递值的副本。
正如评论中Benjamin Lindley所指出的那样,如果您接受这种方式,您可以编写上述构造函数的重载,通过引用来传递它们的参数。
struct A
{
    A(string const& s) : _s(s) { }    // 1 copy
    A(string&& s) : _s(move(s)) { }   // 1 move
private:
    string _s;
};

上述版本允许对lvalue执行一次复制,对rvalue执行一次移动,而传递值的版本始终执行一次额外的移动。因此,如果移动是参数类型的昂贵操作(这不是string的情况,但可能是其他类型的情况),则此解决方案可能更可取。
然而,如果您的函数需要多个参数,则这样做可能很麻烦。为了减少工作量,您可以编写一个接受通用引用并完美转发其参数的单个函数模板。这个在StackOverflow上的问答与此主题相关。

@juanchopanza:是的,绝对是:忽略了那个。谢谢。 - Andy Prowl
1
“没有理由通过常量引用传递s”-- 这并不完全正确。如果你想避免编写多个几乎相同的构造函数,那么通过值传递再移动是一个很好的选择,特别是当有多个参数时,因为复制是指数级的。但是,如果您愿意编写额外的重载,您将从编写一个版本中受益,该版本采用const引用,然后进行复制,另一个版本采用r-value引用,然后进行移动。 - Benjamin Lindley
@BenjaminLindley:没错,但在这种情况下,我宁愿建议编写一个接受通用引用(并完美转发其参数)的模板版本,以避免重复。不是吗? - Andy Prowl
@Benjamin:无论如何,我进一步讨论了这个问题,感谢你的贡献。 - Andy Prowl
也许吧。我在这方面没有太多经验,但在我看来,你会冒着调用不必要的构造函数和出现难懂错误消息的风险。此外,您的代码将无法自我说明。 - Benjamin Lindley
@BenjaminLindley:这也是真的。另一方面,如果你的构造函数需要3个参数,那么你需要8个重载来覆盖所有可能的组合。在我看来,这是一个更糟糕的问题,但当然这取决于个人的情况和偏好。 - Andy Prowl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接