使用std :: map <k,v>,其中V没有可用的默认构造函数</k,v>

时间:2009-12-20 07:35:38

标签: c++ stl map compile-time

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

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


BTW:我知道我可以使用Map.insert(map<K,V>::value_type(k,v))插入地图。


编辑:有几个人提出的解决方案相当于改变了值的类型,这样地图就可以构建一个而不需要调用默认构造函数。 这与我想要的结果完全相反,因为它会将错误隐藏到以后。如果我愿意这样做,我可以简单地从构造函数中删除断言。我想要的目的是让错误更快发生;在编译时。然而,似乎没有办法区分operator[]的r值和l值使用,所以看起来我想要的东西不能这样做,所以我只需要省去一起使用它

10 个答案:

答案 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,如果键丢失,它会抛出,而不是默认构造一个。或者您可以使用返回迭代器的 findlower_boundequal_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);
    }
}