我有std::unordered_map<std::tuple<A, A>, B> map;
。我有一个修改这样的地图的功能
void modify(const A& a1, const A& a2)
{
map[/* a1, a2 */].modify();
}
现在我有点担心A
的不必要的副本。这是我的尝试。
map[{a1, a2}].modify();
它看起来很干净,但它从a1
,a2
的副本构建临时密钥(元组)。
map[std::tie(a1, a2)].modify();
这看起来很有希望,因为它构建了std::tuple<const A&, const A&>
并将其传递给地图的operator[]
。我的地图的operator[]
签名是
B& operator[](const std::tuple<A, A>&)
B& operator[](std::tuple<A, A>&&)
哪个与std::tie
的返回类型不匹配,但它有效。所以我看一下std::tuple
的构造函数,并发现转换构造函数,这让我想到,副本仍然是(so I tested it)。
有没有办法查询地图,没有任何不必要的副本,仍然保留O(1)平均查找复杂度?
答案 0 :(得分:5)
我最好的猜测是你不能避免在这里复制。整个问题归结为这样的事情:
A x1;
std::tuple<A&> t1{x1};
const std::tuple<A>& t2{t1};
const std::tuple<A>& t3{std::tuple<A&>{x1}};
t2
和t3
的两种结构都会调用A
的复制构造函数(实时演示:https://wandbox.org/permlink/MxTUb61kO3zL3HmD)。
如果您真的关心性能并且A
的实例复制起来很昂贵,您可以将它们放入某个池(例如std::vector<A>
),然后只将指针放入地图中({ {1}})。
<强>更新强>
请注意,您还可以设计自己的“元组/对”类,可以通过值或引用来构造。一个非常基本的解决方案可能如下:
std::unordered_map<std::tuple<A*,A*>,B>
在map中的元素访问期间,它的用法不会触发任何复制/移动构造函数:
struct construct_from_ref_tag { };
template <typename T> class ref_pair {
public:
ref_pair(T v1, T v2)
: v1_(std::move(v1)), v2_(std::move(v2)), r1_(v1_), r2_(v2_) { }
ref_pair(const T& r1, const T& r2, construct_from_ref_tag)
: r1_(r1), r2_(r2) { }
bool operator==(const ref_pair<T>& rhs) const {
return ((r1_ == rhs.r1_) && (r2_ == rhs.r2_)); }
size_t hash() const {
return std::hash<T>{}(r1_) ^ std::hash<T>{}(r2_); }
private:
T v1_, v2_;
const T& r1_;
const T& r2_;
};
namespace std {
template <typename T> struct hash<ref_pair<T>> {
size_t operator()(const ref_pair<T>& v) const { return v.hash(); }
};
}
我不喜欢它,但它有效。 std::unordered_map<ref_pair<A>, int> map;
map[ref_pair<A>(1, 2)] = 3;
A a1{1};
A a2{2};
std::cout << "before access" << std::endl;
map[ref_pair<A>(a1, a2, construct_from_ref_tag{})] += 1;
必须是默认构造的,默认构造函数应该是便宜的。现场演示:https://wandbox.org/permlink/obSfPEJXn3Yr5oRw。
答案 1 :(得分:0)
我认为你不能避免复制。我没有使用元组,而是编写了一个基本的模板对类,并使用移动语义将其替换为您网站上的代码,这就是我提出的:
template<typename T>
struct Pair {
T t1;
T t2;
Pair<T>() : t1(), t2() {}
Pair<T>( T a, T b ) : t1( std::move(a) ), t2( std::move(b) ) {}
Pair<T>( const Pair<T>& p ) : t1( std::move( p.t1 ) ), t2( std::move( p.t2 ) ) {}
Pair<T>( Pair<T>&& p ) : t1( std::move( p.t1 ) ), t2( std::move( p.t2 ) ) {}
Pair<T>& operator=( const Pair<T>& p ) {
t1 = std::move( p.t1 );
t2 = std::move( p.t2 );
return *this;
}
Pair<T>& operator=( Pair<T>&& p ) {
t1 = std::move( p.t1 );
t2 = std::move( p.t2 );
return *this;
}
};
struct verbose {
explicit verbose( int val ) : val( val ) { std::cout << " default ctor\n"; }
~verbose() { std::cout << val << " dtor\n"; }
verbose( const verbose& v ) : val( v.val ) { std::cout << val << " copy ctor\n"; }
verbose( verbose&& v ) : val( v.val ) { std::cout << val << " move ctor\n"; }
verbose& operator=( const verbose& v ) {
val = v.val;
std::cout << val << " copy assign\n";
return *this;
}
verbose& operator=( verbose&& v ) {
val = v.val;
std::cout << val << " move assign " << std::endl;
return *this;
}
int val;
};
struct verbose_hash {
std::size_t operator()( const Pair<verbose>& v ) const {
return std::hash<int>{}(std::move( v.t1 ).val ^ std::move( v.t2 ).val);
}
};
struct verbose_eq {
bool operator()( const Pair<verbose>& lhs, const Pair<verbose>& rhs ) const {
return (std::move( lhs ).t1).val == (std::move( rhs ).t1).val &&
(std::move( lhs ).t2).val == (std::move( rhs ).t2).val;
}
};
std::unordered_map<Pair<verbose>, int, verbose_hash, verbose_eq> map;
void modify1( const verbose& v1, const verbose& v2 ) {
map[{v1, v2}] = 1;
}
void modify2( const verbose& v1, const verbose& v2 ) {
//map[std::tie(v1, v2)] = 1; // won't work not using tuple
}
int main() {
map.emplace( std::move( Pair<verbose>( verbose(1) , verbose(2) ) ), std::move( 0 ) );
std::cout << "After emplace\n";
verbose v1( 1 ), v2( 2 );
std::cout << "Before lookup1\n";
modify1( std::move(v1), std::move(v2) );
std::cout << "Before dtors\n";
std::cout << "\nPress any key and enter to quit.\n";
std::cin.get();
return 0;
}
输出:
default ctor
1 move ctor
2 move ctor
1 dtor
2 dtor
1 move ctor
2 move ctor
2 dtor
1 dtor
After emplace
default ctor
default ctor
Before lookup1
2 copy ctor // copy
1 copy ctor // copy
1 move ctor
2 move ctor
1 dtor
2 dtor
2 dtor
1 dtor
Before dtors
代码使用移动语义,但因为函数通过const引用绑定到左值;你不会远离副本。