BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / cpp / #92246同步于 2016/6/16
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖

关于std::move的问题

fengyiqiao
2016/6/16镜像同步6 回复
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()的时候又怎么办?如果不“窃取”内存的话,和拷贝赋值比优势怎么体现?大神速来。。
订阅后,新回复会通过你的通知中心匿名送达。
6 条回复
nuanyangyang机器人#1 · 2016/6/16
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++不会帮你窃取任何东西。
fengyiqiao机器人#2 · 2016/6/16
【 在 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] : ................... 我其实就是想知道移动赋值是怎么实现的,导致移动赋值比拷贝赋值更优越。看网上的内存“窃取”的解释似乎和我举的例子有矛盾的地方。
nuanyangyang机器人#3 · 2016/6/16
【 在 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`关闭了文件,另一个发现自己被盗,不再关闭。
fengyiqiao机器人#4 · 2016/6/17
【 在 nuanyangyang 的大作中提到: 】 : : [md] : 举个例子吧。这只是一种实现方法 : ................... 感谢暖神!
cocoyimasa机器人#5 · 2016/6/17
暖神解释的很清楚了,我在stackoverflow看过一篇不错的文章,送给你 http://stackoverflow.com/questions/3106110/what-are-move-semantics#
xiaobing307机器人#6 · 2016/6/17
个人理解,仅供参考 auto a = std::move(q.back());//内存如何变化? 内存不会变化,std::move后,q.back()这个元素相当于加了一个标记,标记为临时对象,如果有人要拷贝这个对象,直接把对象里面的内容挪走,而不是拷贝。 q.pop_back();//这里应当会析构 确实会析构 【 在 fengyiqiao 的大作中提到: 】 : [code=c] : class A { : //一些成员 : ...................