为什么std::map只接受左值引用的自定义比较器?



我想知道为什么像mapunordered_map这样的STL类只通过const左值引用而不通过右值引用来使用自定义比较器和哈希器对象。

unordered_map( size_type bucket_count,
const Hash& hash,
const Allocator& alloc )
: unordered_map(bucket_count, hash, key_equal(), alloc) {}
template< class InputIt >
map( InputIt first, InputIt last,
const Compare& comp = Compare(),
const Allocator& alloc = Allocator() );

在大多数情况下,这无关紧要,因为这些对象通常是轻量级的,但如果它们碰巧非常重而无法复制呢?

#include <iostream>
#include <string>
#include <unordered_map>
#include <map>
struct Key
{
std::string first, second;
int third;
bool operator==(const Key& other) const
{
return (first == other.first && second == other.second && third == other.third);
}
};
struct KeyHasher
{
KeyHasher(int param) : myparam(param) {}
KeyHasher(const KeyHasher& other) { std::cout << "copy" << std::endl; }
KeyHasher(KeyHasher&& other) { std::cout << "move" << std::endl; }
std::size_t operator()(const Key& k) const
{
using std::size_t;
using std::hash;
using std::string;
return ((hash<string>()(k.first) ^ (hash<string>()(k.second) << 1)) >> 1) ^ (hash<int>()(k.third) << 1);
}
private:
int myparam = 22;
// heavy objects...
};

struct KeyComparator 
{
KeyComparator() = default;
KeyComparator(const KeyComparator& other) { std::cout << "copy" << std::endl; }
KeyComparator(KeyComparator&& other) { std::cout << "move" << std::endl; }
bool operator()(const Key& lhs, const Key& rhs) const 
{
return lhs.third < rhs.third; 
}

// heavy objects...
};
int main()
{
std::map<Key, std::string, KeyComparator> map1( KeyComparator{} );      // can't move key comparator
map1.insert({ {"John", "Galt", 12}, "first" });
map1.insert({ {"Mary", "Sue", 21}, "second" });
std::unordered_map<Key, std::string, KeyHasher> map2{ 33, KeyHasher(729) };     // can't move key hasher
map2.insert({ {"Marie", "Curie", 17}, "radium" });

return 0;
}

是否有理由不像下面这样添加重载:

unordered_map( size_type bucket_count,
Hash&& hash,
Allocator&& alloc ) {}
template< class InputIt >
map( InputIt first, InputIt last,
Compare&& comp = Compare(),
Allocator&& alloc = Allocator() );

关于每个STL容器中使用的分配器的问题。

假设所有函数对象都很小是标准库的一个非常基本的设计决策。如果它们不是,这应该是罕见的情况,责任转移到客户端代码-您可以使用std::reference_wrapper,闭包引用堆上某处的重状态(想想[heavyObject = std::make_unique<SomeType>()](...) { /* ... */ }等)。

作为参考,请参见Scott Meyers的"Effective STL", named "Design functor classes for passing -by-value":

STL函数对象是在函数指针之后建模的,因此STL中的约定是,函数对象在传递给函数和从函数传递时也是按值传递(即复制)的。

在非常常见的情况下,您的意思可能不明确。你只能把它们作为非默认参数。

即使你没有这些参数的默认值,你现在已经加倍了已经大量的重载。