实现移动构造函数的类使用std::sort报错,能否仅按部分元素排序?
问题1:如何针对实现了移动构造函数的类使用std::sort?
你的编译错误error: use of deleted function 'myclass & myclass::operator=(...,核心原因是C++17之前的std::sort要求元素类型满足「可移动赋值(MoveAssignable)」的约束。虽然你的myclass实现了移动构造函数,但如果它的移动赋值运算符被删除了(比如类包含不可赋值的成员、显式删除了operator=,或者因为显式声明了析构/复制构造等导致编译器无法自动生成移动赋值),那么std::pair<int, myclass>的移动赋值运算符也会被编译器删除——因为pair的赋值操作依赖于两个成员的赋值能力,这就导致std::sort在尝试交换/赋值元素时失败。
解决这个问题有两种常见思路:
思路1:给myclass补充移动赋值运算符
如果你的myclass没有特殊的成员变量(比如没有const成员、引用成员等),可以让编译器自动生成默认的移动赋值:
class myclass { public: // 已有的移动构造函数 myclass(myclass&&) noexcept = default; // 添加默认移动赋值 myclass& operator=(myclass&&) noexcept = default; // 其他成员... };
如果需要自定义逻辑,也可以手动实现移动赋值运算符,确保它能正确转移资源。
思路2:升级到C++17标准编译
C17对std::sort的约束做了放宽,不再要求元素可移动赋值,只需要满足**可移动构造(MoveConstructible)和可交换(Swappable)**即可。如果你的编译器支持C17,只需要在编译时加上-std=c++17(GCC/Clang)或/std:c++17(MSVC)参数,原代码就能正常编译运行——因为你的myclass已经有移动构造函数,且std::pair的交换操作可以通过移动构造来完成。
问题2:是否可以不使用整个元素进行std::sort?
当然可以!如果你的需求是仅根据元素的部分字段排序,但保留元素的完整性,有两种实用的实现方式:
方式1:基于索引排序(C++11及以上可用)
先创建元素的索引列表,根据目标字段排序索引,最后根据排序后的索引重新构造原容器。这种方式避免了直接移动大元素(如果元素体积很大的话更高效),同时完全基于字段排序:
#include <numeric> // 用于std::iota std::vector<std::pair<int, myclass>> unsorted_vec; // 填充unsorted_vec的代码... // 1. 创建索引容器,初始值为0,1,2,...n-1 std::vector<std::size_t> indices(unsorted_vec.size()); std::iota(indices.begin(), indices.end(), 0); // 2. 根据pair的first字段排序索引 std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) { return unsorted_vec[a].first < unsorted_vec[b].first; }); // 3. 根据排序后的索引,移动构造新的有序容器 std::vector<std::pair<int, myclass>> sorted_vec; sorted_vec.reserve(unsorted_vec.size()); for (auto idx : indices) { sorted_vec.push_back(std::move(unsorted_vec[idx])); } // 替换原容器 unsorted_vec = std::move(sorted_vec);
方式2:使用C++20范围视图(更简洁)
如果你的环境支持C++20,可以利用std::ranges的视图功能,直接基于字段排序,底层依然会正确处理元素的移动:
#include <ranges> // 直接对原容器按first字段排序,无需额外索引 std::ranges::sort(unsorted_vec, {}, &std::pair<int, myclass>::first);
这里的第三个参数指定了排序的键(即pair的first成员),std::ranges::sort会自动基于这个键完成排序,同时处理元素的移动逻辑。
内容的提问来源于stack exchange,提问作者Robert

