首页 > 解决方案 > 在面向值的属性设置器中使用 std::move() 是否有任何陷阱?

问题描述

我遇到了这个关于如何编写 C++ 获取器/设置器的答案,作者暗示当涉及到面向值的属性时,标准库中的设置器使用std::move()这样的......

class Foo
{
    X x_;
public:
    X x() const { return x_; }
    void x(X x) { x_ = std::move(x); }
}

(直接取自上述答案的代码)

...利用移动运算符(如果指定),可能会提高性能。

这本身对我来说很有意义 - 当值通过值传递给方法时,值被复制一次,因此如果可以移动它们,则无需再次将它们复制到属性。但是,我对 C++ 的了解还不足以确定在所有情况下这样做都是安全的。

按值传递参数是否总是进行深层复制?还是取决于对象?考虑到std::move()所谓的“向编译器发出信号,我不在乎移动的对象会发生什么”,如果我打算保留原始对象,这可能会产生意想不到的副作用。

如果这是一个众所周知的问题,我深表歉意,我正在学习 C++,并且真的想深入了解该语言。

标签: c++getterc++-standard-library

解决方案


就在这里。

如果您始终向该参数发送右值,则按值接收参数并移动是可以的。发送左值也可以,但会比通过 const ref 接收要慢,尤其是在循环中。

为什么?似乎不是复制,而是简单地复制然后移动,其中移动在性能方面微不足道。

的。

您假设复制分配与复制构造函数一样慢,这是错误的。

考虑一下:

std::string str_target;
std::string long1 = "long_string_no_sso hello1234";
std::string long2 = "long_string_no_sso goobye123";

str_target = long1; // allocation to copy the buffer
str_target = long2; // no allocation, reuse space, simply copy bytes

这就是为什么对于 setter 函数,默认情况下,您应该默认通过 const ref 接收,并添加一个右值 ref 以优化右值:

class Foo
{
    X x_;
public:
    X x() const { return x_; }

    // default setter, okay in most cases
    void x(X const& x) { x_ = x; }

    // optionally, define an overload that optimise for rvalues
    void x(X&& x) noexcept { x_ = std::move(x); }
};

唯一不适用的地方是构造函数参数和其他接收器函数,因为它们总是构造并且没有可重用的缓冲区:

struct my_type {
    // No buffer to reuse, this->_str is a totally new object
    explicit my_type(std::string str) noexcept : _str{std::move(str)}

private:
    std::string _str;
};

推荐阅读