std :: move改变变量的地址?

时间:2016-08-28 14:59:42

标签: c++ c++11 move

我发现了意外(至少对我而言)的行为。

class A
{
    char  _text[100];
    char* _beg;
    char* _end;
public:
    explicit A(char* text, size_t tsize) : _beg(&text[0]), _end(&text[std::min(tsize, 99)]) 
    {
        memcpy(_text, text, std::min(tsize, 99));
        *_end = '\0';
    }
    inline std::string get_text()
    {
        return std::move(std::string(_beg, _end));  
    }
};

在代码的某处后,我这样做:

  A* add_A(A&& a)
  {
     list_a.push_back(std::move(a));
     return &(list_a.back());
  }

  std::list<A> list_a;
  {
      add_A(A("my_text", 7));
      list_a.back().get_text(); //returns "my_text"
  }
  list_a.back().get_text(); //returns trash

因为只有我移动这个类(使用std::move),并且调用被移动的对象的get_text(),我才会得到垃圾,好像在变量_text的移动地址发生变化之后,所以_beg_end指向无处。

std::move之后,变量的地址是否真的可以改变(我认为move并没有真正移动对象,它是为此发明的?)

如果可以更改,处理它的常用模式是什么(相应地更改指针)?

如果无法更改,可能会发生这种行为,因为我试图将此类对象移动到std::list(因此在某种程度上会发生复制,它会更改变量的地址并使指针指向错误的位置)?

2 个答案:

答案 0 :(得分:3)

在C ++中移动只是一种特殊的复制形式,您可以在其中修改要移动的对象中的数据。这就是unique_ptr的工作方式;将指针从一个unique_ptr对象复制到另一个,然后将原始值设置为NULL。

当您移动对象时,您正在创建一个新对象,一个从另一个对象获取其数据的对象。成员的地址不会“改变”;它只是不是同一个对象

因为您没有编写复制/移动构造函数,这意味着编译器会为您编写一个。他们所做的只是复制每个元素。因此,新移动的对象将具有指向指向旧​​对象的指针

即将被摧毁的物品。

这就像进入一个看起来与旧的房子相同的房子。无论它看起来像你的老房子多少,它都不是。你仍然需要改变你的地址,因为这是一所新房子。 <{1}}和_beg的地址也必须更新。

现在,您可以创建一个移动构造函数/赋值运算符(以及一个复制构造函数/赋值运算符)来更新指针。但坦率地说,这只是贴在糟糕的设计上。如果可以帮助它,那么在同一个对象中指向子对象并不是一个好主意。而不是开始/结束指针,只需要一个实际的大小

_end

这样,就不需要存储开始/结束指针。这些可以根据需要合成。

答案 1 :(得分:2)

std::move没有可移动的部分,它只是将输入参数提升为右值引用 - 请记住,在foo(T&& t) { ... }的主体内部,t按名称使用评估为左值(参考右值)。

inline std::string get_text()
{
    return std::move(std::string(_beg, _end));
}

打破这个局面:

std::string(_beg, _end);

创建一个从_beg_end构造的匿名临时std :: string对象。这是一个右值。

std::move(...);

强制将其提升为右值引用,并阻止编译器执行返回值优化。你想要的是

return std::string(_beg, _end);

请参阅assembly code comparison

您可能还想使用

list_a.emplace_back(std::move(a));

不幸的是,这种方法存在两个缺陷。

更简单的是,术语moving可能有点误导,听起来很单一。但实际上它通常是双向交换:两个对象交换属性,这样当临时对象超出范围时,它会执行以前拥有的其他对象的清理:

struct S {
    char* s_;
    S(const char* s) : s_(strdup(s)) {}
    ~S() { release(); }
    void release() { if (s_) free(s_); }
    S(const S& s) : s_(strdup(s.s_)) {}
    S(S&& s) : s_(s.s_) { s.s_ = nullptr; }
    S& operator=(const S& s) { release(); s_ = strdup(s); return *this; }
    S& operator=(S&& s) { std::swap(s_, s.s_); return *this; }
};

请注意以下这一行:

    S& operator=(S&& s) { std::swap(s_, s.s_); return *this; }

当我们写:

S s1("hello");
s1 = S("world");

第二行调用move-assignment运算符。 hello副本的指针移入临时,临时超出范围并被销毁,“hello”的副本被释放。

使用您的字符数组进行此交换的效率远低于单向副本:

struct S {
    char s_[100];
    S(const S& s) {
        std::copy(std::begin(s.s_), std::end(s.s_), std::begin(s_));
    }
    S(S&& s) {
        char t_[100];
        std::copy(std::begin(s.s_), std::end(s.s_), std::begin(t_));
        std::copy(std::begin(s_), std::end(s_), std::begin(s.s_));
        std::copy(std::begin(t_), std::end(t_), std::end(s_));
    }
};

你没有来执行此操作,rvalue参数只需要处于安全销毁状态,但上面是默认移动操作符是什么要去做。

代码的灾难部分是默认移动运算符是天真的。

struct S {
    char text_[100];
    char *beg_, *end_;
    S() : beg_(text_), end_(text_ + 100) {}
};

考虑以下复制构造:

S s(S());

s.beg_指向什么?

答案:它指向S().text_,而不是s.text_。您需要编写一个复制构造函数,复制text_的内容,然后将自己的beg_end_指向自己的text_,而不是复制源值。 / p>

移动运算符出现同样的问题:它将移动 text_的内容,但它也会移动指针,并且不知道他们是亲戚。

您需要编写复制/移动构造函数和赋值运算符,或者您可以考虑将beg_end_替换为单个size_t大小值。

但在任何一种情况下,移动都不是你的朋友:你没有转让所有权或执行浅拷贝,你的所有数据都在你的对象中。