返回信息流class A {
//一些成员
};
std::vector<A> q;
//q里加入了一些元素
auto a = std::move(q.back());//内存如何变化?
q.pop_back();//这里应当会析构
A tmp;
q.push_back(tmp);
就想问下移动赋值的时候内存怎么变的?看到的貌似说是“窃取”了原本的内存,但pop_back()的时候析构对象怎么办?如果不析构,push_back()的时候又怎么办?如果不“窃取”内存的话,和拷贝赋值比优势怎么体现?大神速来。。
这是一条镜像帖。来源:北邮人论坛 / cpp / #92246同步于 2016/6/16
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
关于std::move的问题
fengyiqiao
2016/6/16镜像同步6 回复
订阅后,新回复会通过你的通知中心匿名送达。
6 条回复
std::move只是将参数的类型转换成T&&而已。效果就是a = std::move(q.back())的时候调用了a的A& operator=(A&& a)方法而已。并不会造成q.back()提前析构。
其实,如果只是a = q.back(),C++可以调用A& A::operator=(const A& a)或者A& A::operator=(A&& a)。如果A同时支持“拷贝赋值”(前者)和“移动赋值”(后者),那么默认会调用前者。这个情况下,就靠强制转换等号右边的表达式的类型,来区别这两个函数。
a = q.back(); // 因为q.back()是l-value,所以默认会把它当成A&型,调用A::operator(const A& a)
a = static_cast<A&&>(q.back()); // 因为有强制类型转换的存在,等号右边是A&&型的。所以调用A::operator(A&& a)
a = std::move(q.back()) // 效果和上一行一样。其实std::move就是一个static_cast。
至于A::operator=(A&& a)方法如何“窃取”q.back()里面的“内存”(其实,“窃取”的并不一定是内存,可以是别的东西),就看A& A::operator=(A&& a)如何实现了。
所以,答案就是:一切看A::operator=(A&& a)如何实现。C++不会帮你窃取任何东西。
【 在 nuanyangyang 的大作中提到: 】
: std::move只是将参数的类型转换成T&&而已。效果就是a = std::move(q.back())的时候调用了a的A& operator=(A&& a)方法而已。并不会造成q.back()提前析构。
: 其实,如果只是a = q.back(),C++可以调用A& A::operator=(const A& a)或者A& A::operator=(A&& a)。如果A同时支持“拷贝赋值”(前者)和“移动赋值”(后者),那么默认会调用前者。这个情况下,就靠强制转换等号右边的表达式的类型,来区别这两个函数。
: [code=cpp]
: ...................
我其实就是想知道移动赋值是怎么实现的,导致移动赋值比拷贝赋值更优越。看网上的内存“窃取”的解释似乎和我举的例子有矛盾的地方。
【 在 fengyiqiao 的大作中提到: 】
: 我其实就是想知道移动赋值是怎么实现的,导致移动赋值比拷贝赋值更优越。看网上的内存“窃取”的解释似乎和我举的例子有矛盾的地方。
举个例子吧。这只是一种实现方法
```cpp
#include <cstdio>
#include <cstdlib>
#include <string>
#include <memory>
using namespace std;
class CFileWrapper {
private:
FILE *fp;
public:
// 默认构造函数。创建一个并没有资源的对象。
CFileWrapper() {
printf("Creating empty CFileWrapper...\n");
fp = nullptr;
}
// 打开一个文件。
CFileWrapper(string filename) {
printf("Openning file...\n");
fp = fopen(filename.c_str(), "rb"); // 为了简便,就用只读方式打开。
if (fp == nullptr) {
perror("Cannot open file");
exit(1);
}
printf("File opened.\n");
}
// 析构函数。析构的时候自动关闭文件,除非文件被偷。
virtual ~CFileWrapper() {
if (fp != nullptr) {
printf("Closing file...\n");
fclose(fp);
printf("File closed.\n");
} else {
printf("File is stolen. Don't close.\n");
}
}
// 删除默认的“拷贝赋值”函数。其实,即使我省略这一行,C++看我定义了“移动
// 赋值”函数,就会自动帮我删除这个拷贝赋值函数,除非我自己定义一个拷贝赋
// 值函数。当然,我不想定义,因为我的政策就是文件不可以拷贝。
CFileWrapper& operator =(const CFileWrapper& victim) = delete;
// 这个参数为&&型的就是“移动赋值”函数。
CFileWrapper& operator =(CFileWrapper&& victim) {
if (fp != nullptr) { // 如果自己仍然持有资源,就要先关闭。
printf("Alread holding file. close it...\n");
fclose(fp); // 因为自己是唯一一个拥有资源的。
}
printf("Stealing file...\n");
fp = victim.fp; // 偷它的fp。
victim.fp = nullptr; // 然后把它的fp设置成nullptr,让它知道自己被偷了。
}
size_t read(void *ptr, size_t size, size_t nmemb) {
// 读数据。为了简便,一旦出错就致命退出。
if (fp == nullptr) {
fprintf(stderr, "File is stolen!\n");
exit(1);
}
return std::fread(ptr, size, nmemb, fp);
}
};
int main() {
CFileWrapper file("example.txt");
CFileWrapper file2;
// file2 = file; // 出错:use of deleted function ‘constexpr CFileWrapper& CFileWrapper::operator=(const CFileWrapper&)’
file2 = move(file);
char buf[256];
// size_t nread = file.read(buf, 1, 256); // file已被偷。异常终止:File is stolen
size_t nread = file2.read(buf, 1, 256);
fwrite(buf, 1, nread, stdout);
return 0;
}
```
我简单封装了一下C语言的`fopen`之类的函数。这里,`CFileWrapper`里的“资源”是文件,构造的时候打开文件,析构的时候关闭文件。**我的**政策是:*文件不可以共享,同一时间只能有一个`CFileWrapper`拥有这个文件*,但是,*允许把文件在多个`CFileWrapper`之间转移所有权*。这样。只有那个持有文件的对象被析构的时候,文件才会被关闭。这样就避免了文件被关闭两次。
这个“转移所有权”的操作,**我用**赋值运算符实现(言外之意就是这个方法不一定叫`operator=`,可以叫别的名字,比如`steal`。这也说明,在C++里,“赋值”只不过是调用`operator=`方法而已)。在赋值的时候,如果等号右边是`CFileWrapper&&`类型的,就调用“移动赋值”函数。而它“窃取”资源的方法就是把资源fp拷贝到自己这里,把对方的fp设置为nullptr。
对于C++来说,所有的对象到了生存期结束,都要析构的。如果持有文件,析构的时候要把文件关闭;但是因为文件可能被偷,析构的时候就要确认一下,看看自己到底有没有被偷。如果被偷了,就不要关闭文件了。
`main`函数里,第一个文件`file`是给了文件名,直接打开的;而第二个文件`file2`是从`file`那里赋值得到的。因为`CFileWrapper`只定义了“移动赋值”函数,所以不用`std::move`的话C++会抱怨找不到拷贝赋值函数。
如果`example.txt`的内容是"Hello world!",那么运行结果如下:
```text
Openning file...
File opened.
Creating empty CFileWrapper...
Stealing file...
Hello world!
Closing file...
File closed.
File is stolen. Don't close.
```
其中一个`CFileWrapper`关闭了文件,另一个发现自己被盗,不再关闭。
【 在 nuanyangyang 的大作中提到: 】
:
: [md]
: 举个例子吧。这只是一种实现方法
: ...................
感谢暖神!
暖神解释的很清楚了,我在stackoverflow看过一篇不错的文章,送给你
http://stackoverflow.com/questions/3106110/what-are-move-semantics#
个人理解,仅供参考
auto a = std::move(q.back());//内存如何变化?
内存不会变化,std::move后,q.back()这个元素相当于加了一个标记,标记为临时对象,如果有人要拷贝这个对象,直接把对象里面的内容挪走,而不是拷贝。
q.pop_back();//这里应当会析构
确实会析构
【 在 fengyiqiao 的大作中提到: 】
: [code=c]
: class A {
: //一些成员
: ...................