仅通过移动构造函数向std::map插入类实例,为何局部实例仍会被销毁?
问题:仅支持移动语义的类插入std::map后局部实例仍被销毁?
我有一个未实现默认构造函数与拷贝构造函数、仅实现移动构造函数的类,希望在函数内将该类的新实例插入std::map。但使用std::move进行插入后,函数作用域结束时我的局部实例仍会被销毁,移动操作不是应该将实例所有权转移给容器吗?
class Server { public: friend class Client; class Client { public: Client() = delete; Client(const Client &) = delete; Client(Client &&) = default; ~Client() { ::close(m_fd); } Client & operator =(const Client &) = delete; Client & operator =(Client &&) = default; private: Client(int fd, const in_addr & addr) : m_fd(fd), m_addr(addr) {} int m_fd; in_addr m_addr; // 修正原代码成员变量名笔误 }; private: void m_acceptClient() { sockaddr_in addr; socklen_t addrLen = sizeof(addr); int fd = accept4( m_fd, reinterpret_cast<sockaddr *>(&addr), &addrLen, SOCK_NONBLOCK ); if (fd == -1) { if (errno == EAGAIN) return; m_throwError(AcceptException()); return; } auto client = Client(fd, addr.sin_addr); m_clients.emplace(fd, std::move(client)); // 此处client会被销毁 // // 我也尝试过这样写 // m_clients.emplace(fd, Client(fd, addr.sin_addr)) // 但该client也会立即被销毁 // 我想要实现的是无需实现拷贝语义,就能将新的Client插入map // 因为在这个场景下拷贝语义毫无意义 } int m_fd; std::map<int, Client> m_clients; // 补充原代码遗漏的成员变量 };
核心原因与解决方案
为什么局部实例会被销毁?
移动构造的本质是转移资源所有权,但原对象本身依然会遵循C++作用域规则被销毁。默认的移动构造函数只会浅拷贝成员变量,这会导致:
- 容器内的新
Client对象和局部client对象持有同一个文件描述符m_fd - 局部对象销毁时,析构函数调用
::close(m_fd),直接让容器内对象的文件描述符失效,后续操作会触发错误
修复方案
方案1:手动实现移动构造,重置原对象资源标识
默认移动构造不会修改原对象的资源状态,需要手动实现移动构造函数,将原对象的m_fd置为无效值(如-1),确保析构函数不会重复关闭资源:
Client(Client &&other) noexcept : m_fd(other.m_fd), m_addr(other.m_addr) { other.m_fd = -1; // 转移所有权后,原对象的fd置为无效 } // 优化析构函数,仅关闭有效fd ~Client() { if (m_fd != -1) { ::close(m_fd); } }
方案2:直接在map中构造对象,避免局部实例
利用std::map::emplace的原位构造能力,直接传入Client的构造参数,跳过局部对象的创建,从根源上避免析构问题:
// 替换原局部对象创建与emplace代码 m_clients.emplace( std::piecewise_construct, std::forward_as_tuple(fd), // 构造键的参数 std::forward_as_tuple(fd, addr.sin_addr) // 构造Client的参数 );
这种方式会直接在map的节点内存中构造Client对象,不需要移动操作,也就不存在局部对象析构的问题。
内容的提问来源于stack exchange,提问作者Fayeure
相关产品推荐
相关产品推荐

