std::unique仅移除相邻重复元素,必须先排序才能实现全量去重;它返回新逻辑尾迭代器,需配合erase才能真正删除;不改变容器大小,也不调用析构或移动构造函数。

std::unique 只能处理相邻重复项,必须先排序
很多人以为 std::unique 能直接去重整个 std::vector,结果发现没效果——它只移除「连续相同」的元素,对无序容器完全无效。比如 {1,3,2,3,1} 经 std::unique 后仍是原样,因为没有相邻重复。
所以正确流程是:先用 std::sort 排序,再用 std::unique,最后调用 erase 真实删除尾部冗余元素。
-
std::sort是必需前置步骤;不排序,std::unique什么都删不掉 -
std::unique不改变容器大小,只把重复项“挪到末尾”,返回新逻辑结尾的迭代器 - 必须配合
vec.erase(uniq_iter, vec.end())才算真正删除
std::unique 的返回值不是 bool,而是迭代器
常见错误是把它当成功/失败函数来用:if (std::unique(...)) { ... } ——这编译都过不了。std::unique 返回的是「去重后新序列的尾后迭代器」,类型是 std::vector<t>::iterator</t>。
正确写法要分两步:先保存返回值,再擦除:
auto new_end = std::unique(vec.begin(), vec.end()); vec.erase(new_end, vec.end());
- 如果忽略
new_end直接链式调用(如vec.erase(std::unique(...), vec.end())),语法合法但可读性差,且无法复用该迭代器 - 对
const容器或只读视图,std::unique不能用——它需要可写迭代器
自定义比较时,lambda 必须满足严格弱序(strict weak ordering)
若 vector 存的是自定义结构体,或想按绝对值、忽略大小写等规则去重,需传入比较函数。但别随便写 (a, b) { return abs(a) == abs(b); } ——std::unique 的比较谓词要求判断「是否应被合并到前一项之后」,本质是判断 a 是否等于前一个保留项,因此必须用 == 语义,且底层依赖等价关系。
更安全的做法是:用 std::unique 配合 == 重载,或先用 std::sort + 自定义 operator,再调默认 <code>std::unique:
struct Point { int x, y; };
bool operator==(const Point& a, const Point& b) {
return a.x == b.x && a.y == b.y;
}
// ...
std::sort(pts.begin(), pts.end()); // 需同时定义 operator
- 传 lambda 时,参数类型必须和迭代器解引用类型一致(常为
const T&) - 不要在比较函数里修改元素状态,
std::unique不保证调用顺序和次数 - 若仅需去重不关心顺序,用
std::set或std::unordered_set更直观,但会丢失原始顺序且开销更大
性能与移动语义:std::unique 不调用析构,也不移动对象
std::unique 内部只是覆盖(assignment)或交换(swap)相邻元素,不会调用任何对象的析构函数,也不会触发移动构造。这意味着:
- 对含资源句柄(如文件描述符、裸指针)的类,若仅靠
std::unique去重,可能造成逻辑错误——重复项的资源未被释放 - 移动成本高的类型(如大数组、长字符串),
std::unique的 assignment 仍会发生多次,不如先用std::stable_partition预筛选 - 若 vector 元素是
std::unique_ptr,std::unique会执行operator=,导致所有权转移,原始位置变成空指针——这是预期行为,但需确认业务是否允许
真正容易被忽略的是:排序 + unique 这套组合,时间复杂度是 O(n log n),而用哈希集合边遍历边去重是 O(n)。如果顺序不重要且内存充足,后者反而更简单可靠。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











