关于C++的移动语义的一些看法
今天在写代码的时候遇到了一个编译不过的问题,先贴代码:
1 #include <unordered_set> 2 #include <unordered_map> 3 #include <string> 4 5 class noncopyable 6 { 7 protected: 8 noncopyable() = default; 9 noncopyable(const noncopyable&) = delete; 10 noncopyable& operator=(const noncopyable&) = delete; 11 }; 12 13 class MoveStruct : noncopyable 14 { 15 public: 16 MoveStruct() = default; 17 MoveStruct& operator =(MoveStruct&& other) = default; 18 private: 19 std::unordered_set<std::string> symbols; 20 }; 21 22 int main() 23 { 24 25 std::unordered_map<int, MoveStruct> maps; 26 MoveStruct ms; 27 maps[0] = MoveStruct(); 28 maps[0] = std::move(ms); 29 }
编译不过的原因是提示MoveStruct的拷贝构造被delete了。奇怪的是,我明明声明了移动赋值函数了MoveStruct& operator =(MoveStruct&& other) = default,我调用右值赋值,应该会调用到这个函数啊,为什么会提示我的拷贝构造被delete了呢,我拷贝构造本来就是delete的啊,因为我继承自noncopyable的。按照现在这个样子来看,即使我显示定义了移动赋值函数,编译器好像也没给我生成这个函数么。
于是乎去看了下《C++ Primer 5th》,里面有这么一段话
意思是说在以下这几种情况下,编译器会把拷贝语义的member function默认delete掉。于是我就给我的MoveStruct改了下,将其的移动赋值自定义了一下:
class MoveStruct : noncopyable { public: MoveStruct() = default; MoveStruct(const MoveStruct&) = delete; MoveStruct& operator = (const MoveStruct&) = delete; //改成显示自定义 MoveStruct& operator =(MoveStruct&& other) { return *this; } private: std::unordered_set<std::string> symbols; };
果然编译通过了。看样子是我的移动赋值被编译器默认为delete了。但是我的MoveStruct并没有满足上面的4个要求啊,为什么会被delete掉呢。基类只是将拷贝构造和拷贝赋值delete了,这样做编译器只是不会给你默认生成移动语义member function了,但是不会给你delete掉呀,是不是积累的Protected声明member function的缘故呢。于是我又改了noncopyable的代码:
1 class noncopyable 2 { 3 protected: 4 noncopyable() = default; 5 noncopyable(const noncopyable&) = delete; 6 noncopyable& operator=(const noncopyable&) = delete;
7 noncopyable& operator=(noncopyable&&) = default;
8 noncopyable(noncopyable&&) = default; 9 }; 10 11 12 class MoveStruct : noncopyable 13 { 14 public: 15 MoveStruct() = default; 16 MoveStruct(const MoveStruct&) = delete; 17 MoveStruct& operator = (const MoveStruct&) = delete; 18 MoveStruct& operator =(MoveStruct&& other) = default; 19 private: 20 std::unordered_set<std::string> symbols; 21 };
果然,又能编译过了。按照《C++ Primer 5th》里写的,基类显示声明了拷贝构造和拷贝赋值函数为delete,编译器只是默认不给基类生成移动语义member function,所以基类就变成了uncopyable和unmoveable了,所以子类也就自然不能moveable了。所以上述5点是不是应该加上继承unmoveable基类,子类的移动构造和移动赋值也会delete这个情况呢?
至于为什么类一旦显示声明了拷贝构造和拷贝赋值函数或者析构函数,移动构造和移动赋值默认不会生成应该是C++11新标准为了兼容老代码,因为,如果我们定义了这些操作往往表示类内含有指针成员需要动态分配内存,如果需要为类定义移动操作,那么应该确保移动后源对象是安全的,但是默认的移动构造函数不会帮我们把指针成员置空,移后源不是可析构的安全状态,如果这样,当离开移动构造后,源对象被析构,对象内的指针成员空间被回收,转移之后对象内的指针成员出现悬垂现象,程序将引起致命的错误。所以当我们定义了自己的拷贝操作和析构函数时,编译器是不会帮我们合成默认移动构造函数的。
如果有不对的地方,望指正!