52

我有一个符号表实现为std::map. 对于值,没有办法通过默认构造函数合法地构造值类型的实例。但是,如果我不提供默认构造函数,则会出现编译器错误,并且如果我使构造函数断言,我的程序编译得很好,但map<K,V>::operator []如果我尝试使用它来添加新成员,则会在内部崩溃。

有没有办法让 C++map[k]在编译时禁止作为左值(同时允许它作为右值)?


顺便说一句:我知道我可以使用Map.insert(map<K,V>::value_type(k,v)).


编辑:有几个人提出了相当于改变值类型的解决方案,以便地图可以在不调用默认构造函数的情况下构造一个。这与我想要的结果完全相反,因为它将错误隐藏到以后。如果我愿意,我可以简单地从构造函数中删除断言。我想要的是让错误发生得更快;在编译时。但是,似乎没有办法区分 r-value 和 l-value 的使用,operator[]所以我想要的似乎无法完成,所以我只需要放弃一起使用它。

4

10 回答 10

51

你不能让编译器区分 operator[] 的两种用法,因为它们是同一个东西。Operator[] 返回一个引用,因此分配版本只是分配给该引用。

就个人而言,除了快速和肮脏的演示代码之外,我从不将 operator[] 用于地图。请改用 insert() 和 find()。请注意,make_pair() 函数使 insert 更易于使用:

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) );

即使没有提供复制/移动构造函数。

于 2009-12-20T12:17:20.027 回答
8

使用map<K,V>::at(). map<K,V>::operator []如果提供的键不存在,将尝试默认构造一个元素。

于 2017-03-09T17:51:07.123 回答
5

V没有默认构造函数,因此您不能真正期望 可用。std::map<K,V> std::map<K,V>::operator[]

Astd::map<K, boost::optional<V> > 确实具有mapped_type可默认构造的 a,并且可能具有您想要的语义。有关详细信息,请参阅Boost.Optional文档(您需要了解它们)。

于 2009-12-20T13:03:42.053 回答
4

如果值类型不是默认可构造的,那么operator[]它对你不起作用。

但是,您可以做的是提供免费的函数来获取和设置地图中的值以方便使用。

例如:

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;
    }
}

您也可以考虑像dict.get(key [, default])Python 中的 getter(如果 key 不存在,则返回提供的默认值(但存在可用性问题,即始终必须构造默认值,即使您知道该 key 在映射中)。

于 2009-12-20T15:25:56.603 回答
2

从中派生一个新类std::map<K,V>并创建您自己的operator[]. 让它返回一个 const 引用,它不能用作左值。

于 2009-12-21T21:23:27.487 回答
1

这有点难看,但解决此问题的一种方法是添加一个成员变量来跟踪实例是否有效。您的默认构造函数会将实例标记为无效,但所有其他构造函数会将实例标记为有效。

确保您的赋值运算符正确传输新成员变量。

修改您的析构函数以忽略无效实例。

将所有其他成员函数修改为在对无效实例进行操作时抛出/错误/断言。

然后,您可以在地图中使用您的对象,只要您只使用正确构造的对象,您的代码就可以正常工作。

同样,如果您想使用 STL 映射并且不愿意使用 insert 和 find 而不是 operator[],这是一种解决方法。

于 2009-12-20T08:00:55.487 回答
1

不知道为什么它会为你编译,我认为编译器应该已经捕获了你丢失的构造函数。

怎么用

map<K,V*>

代替

map<K,V> ?
于 2009-12-20T15:58:59.003 回答
1

您无法区分 的左值和右值使用operator[],因为它始终是左值表达式。如果您V使用[].

对于查找,您可以使用at,如果缺少键则抛出,而不是默认构造一个。或者您可以使用find,lower_boundequal_range, 它返回迭代器。

对于赋值,insert_or_assign如果你有 C++17,你可以使用,或者编写一个等效的自由函数:

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);
    }
}
于 2021-04-15T14:25:56.903 回答
0

在 C++ 中使用运算符覆盖时,最好在默认情况下尽可能地遵守运算符的语义。默认的语义。operator[] 是替换数组中的现有成员。似乎 std::map 稍微改变了规则。这很不幸,因为它会导致这种混乱。

请注意,std::map 下 operator[] 的文档(http://www.sgi.com/tech/stl/Map.html)说:“返回对与特定键关联的对象的引用。如果地图尚未包含这样的对象,operator[] 插入默认对象 data_type()。”

我建议您以不同的方式处理替换和插入。不幸的是,这意味着您需要知道哪些是必需的。这可能意味着首先在地图上进行查找。如果性能是一个问题,您可能需要找到一种优化,您可以在其中测试成员资格并通过一次查找插入。

于 2009-12-20T12:09:12.347 回答
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 值引用 (&&)。

于 2009-12-20T15:47:48.900 回答