不,libstdc++ 不执行此优化。
struct A {
A() = default;
A(A&&) { std::format_to(std::ostreambuf_iterator<char>(std::cout), "A(A&&)\n"); }
bool operator==(A const&) const = default;
};
template<> struct std::hash<A> { std::size_t operator()(A const&) const { return 0; } };
int main() {
std::unordered_set<A> s;
A a;
std::format_to(std::ostreambuf_iterator<char>(std::cout), "{}\n",
s.emplace(std::move(a)).second);
std::format_to(std::ostreambuf_iterator<char>(std::cout), "{}\n",
s.emplace(std::move(a)).second);
}
这个程序会打印:
A(A&&)
true
false
在libc++(以及可能是MS-STL)下,但打印
A(A&&)
true
A(A&&)
false
在libstdc++下。
演示。
我在想是否有另一种方法来编写这个 EmplaceOrMerge 函数。
无论如何,libstdc++只会在已经构造的节点上调用哈希函数。如果您不能更改数据结构(例如从提取的键改为 std::unordered_map),则一个选择是使用
node-handle 接口,它可以避免插入失败时的副作用。使用它可能仍然需要移动并移回分配内存的开销,但希望这样的代价相对较小。
template<class T>
auto try_emplace(std::unordered_set<T>& s, std::type_identity_t<T>&& t) {
std::unordered_set<T> s2;
auto nh = s2.extract(s2.insert(std::move(t)).first);
auto const ins = s.insert(std::move(nh));
if (not ins.inserted)
t = std::move(ins.node.value());
return std::pair(ins.position, ins.inserted);
}
演示。
在你的情况下,你可以快捷地移动分配回来,因此开销只有一个移动(和额外的节点分配):
template<typename T>
T& EmplaceOrMerge(std::unordered_set<T>& s,
T&& t,
std::function<void(T&& a, T& b)> merge)
{
std::unordered_set<T> s2;
auto nh = s2.extract(s2.insert(std::move(t)).first);
auto const ins = s.insert(std::move(nh));
T& u = const_cast<T&>(*ins.position);
if (not ins.inserted)
merge(std::move(ins.node.value()), u);
return u;
}
const_cast<T&>(*it.first);
很可疑。像这样修改集合很可疑。std::move
并不会有问题,因为std::move
不做任何事情。它只是转换引用类型,如果emplace
没有实际移动对象,则对象仍然有效,您可以尝试再次移动它。 - Marek Rstd::move
并不会做任何事情,但是emplace
可能会。修改unordered_set
的内容是可以的,只要保留哈希值(这在问题中已经写明)。如果哈希值不是通过emplace计算而来,则参数已被移动。 - V. Semeria