我有一个符号表,实现为std::map
。对于该值,无法通过默认构造函数合法地构造值类型的实例。但是,如果我不提供默认构造函数,我会收到编译器错误,如果我使构造函数断言,我的程序编译得很好,但如果我尝试使用它来添加新成员,则会在map<K,V>::operator []
内崩溃。
有没有办法让C ++在编译时禁止map[k]
作为l值(同时允许它作为r值)?
BTW:我知道我可以使用Map.insert(map<K,V>::value_type(k,v))
插入地图。
编辑:有几个人提出的解决方案相当于改变了值的类型,这样地图就可以构建一个而不需要调用默认构造函数。 这与我想要的结果完全相反,因为它会将错误隐藏到以后。如果我愿意这样做,我可以简单地从构造函数中删除断言。我想要的目的是让错误更快发生;在编译时。然而,似乎没有办法区分operator[]
的r值和l值使用,所以看起来我想要的东西不能这样做,所以我只需要省去一起使用它
答案 0 :(得分:41)
你不能让编译器区分operator []的两个用法,因为它们是同一个东西。 Operator []返回一个引用,因此赋值版本只是分配给该引用。
就个人而言,除了快速而肮脏的演示代码之外,我从不使用operator []作为地图。请改用insert()和find()。请注意,make_pair()函数使插入更易于使用:
m.insert( make_pair( k, v ) );
在C ++ 11中,您也可以
m.emplace( k, v );
m.emplace( piecewise_construct, make_tuple(k), make_tuple(the_constructor_arg_of_v) );
即使没有提供复制/移动构造函数。
答案 1 :(得分:5)
您的V
没有默认构造函数,因此您无法真正期望 std::map<K,V>
std::map<K,V>::operator[]
可用。
std::map<K, boost::optional<V> >
确实具有默认构造的mapped_type
,并且可能具有您想要的语义。有关详细信息,请参阅Boost.Optional文档( 需要了解它们)。
答案 2 :(得分:4)
如果值类型不是默认构造的,那么operator[]
就不适合你了。
但是,您可以做的是提供免费功能,以便在地图中获取和设置值。
E.g:
template <class K, class V>
V& get(std::map<K, V>& m, const K& k)
{
typename std::map<K, V>::iterator it = m.find(k);
if (it != m.end()) {
return it->second;
}
throw std::range_error("Missing key");
}
template <class K, class V>
const V& get(const std::map<K, V>& m, const K& k)
{
typename std::map<K, V>::const_iterator it = m.find(k);
if (it != m.end()) {
return it->second;
}
throw std::range_error("Missing key");
}
template <class K, class V>
void set(std::map<K, V>& m, const K& k, const V& v)
{
std::pair<typename std::map<K, V>::iterator,bool> result = m.insert(std::make_pair(k, v));
if (!result.second) {
result.first->second = v;
}
}
您可能还会考虑使用Python中的dict.get(key [, default])
之类的getter(如果键不存在则返回提供的默认值(但是有一个可用性问题,因为默认情况下总是必须构造,即使您知道键在地图中。)
答案 3 :(得分:3)
从std::map<K,V>
获取新课程并创建自己的operator[]
。让它返回一个const引用,它不能用作l值。
答案 4 :(得分:2)
使用map<K,V>::at()
。如果提供的密钥尚不存在,map<K,V>::operator []
将尝试默认构造元素。
答案 5 :(得分:1)
这有点难看,但解决此问题的一种方法是添加一个跟踪实例是否有效的成员变量。您的默认构造函数会将实例标记为无效,但所有其他构造函数都将该实例标记为有效。
确保您的赋值运算符正确传输新的成员变量。
修改析构函数以忽略无效实例。
将所有其他成员函数修改为在无效实例上运行时抛出/错误/断言。
然后,您可以在地图中使用您的对象,只要您只使用正确构造的对象,您的代码就可以正常工作。
同样,如果您想使用STL映射并且不愿意使用insert和find而不是operator [],这是一种解决方法。
答案 6 :(得分:1)
不确定为什么它会为你编译,我认为编译器应该抓住你缺少的构造函数。
使用
怎么样?map<K,V*>
而不是
map<K,V> ?
答案 7 :(得分:0)
在C ++中使用运算符覆盖时,最好在默认情况下尽可能贴近运算符的语义。默认的语义。 operator []是替换数组中的现有成员。看起来std :: map会稍微弯曲规则。这是不幸的,因为它会导致这种混乱。
请注意,std :: map下的operator []的文档(http://www.sgi.com/tech/stl/Map.html)说:“返回对与特定键关联的对象的引用。如果映射尚未包含此类对象,operator []插入默认对象data_type()。“
我建议您以不同方式对待替换和插入。不幸的是,这意味着您需要知道哪些是必需的。这可能意味着首先在地图上进行查找。如果性能是一个问题,您可能需要找到一个优化,您可以在其中测试成员资格并插入一次查找。
答案 8 :(得分:0)
你可以为你的价值类型专门化std :: map。我不是说这是个好主意,但可以做到。我将scoped_ptr<FILE>
的dtor专用于fclose
而不是delete
。
类似的东西:
template<class K, class Compare, class Allocator>
my_value_type& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k)
{
//...
}
这应该允许您将所需的代码插入到您的类型的operator []中。不幸的是,我不知道当前c ++中只返回r值的方法。在c ++ 0x中,您可以使用:
template<class K, class Compare, class Allocator>
my_value_type&& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k)
{
//...
}
这将返回R值参考(&amp;&amp;)。
答案 9 :(得分:0)
您无法区分 operator[]
的左值和右值用法,因为它始终是左值表达式。如果您使用 V
的替代方案,则不需要将 []
设为可默认构造。
对于查找,您可以使用 at
,如果键丢失,它会抛出,而不是默认构造一个。或者您可以使用返回迭代器的 find
、lower_bound
或 equal_range
。
对于赋值,如果你有 C++17,你可以使用 insert_or_assign
,或者写一个等价的自由函数:
template <typename Map, typename Value = typename Map::mapped_type, typename Key = typename Map::key_type>
void insert_or_assign(Map & map, Key && key, Value && value)
{
auto it = map.lower_bound(key);
if ((it == map.end()) || map.key_comp()(key, it->first)) {
map.emplace(it, std::forward<Key>(key), std::forward<Value>(value));
} else {
it->second = std::forward<Value>(value);
}
}