使用 std::map其中 V 没有可用的默认构造函数

发布于 2024-08-15 15:59:05 字数 588 浏览 3 评论 0 原文

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

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


顺便说一句:我知道我可以使用 Map.insert(map::value_type(k,v)) 插入到地图中。


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

I have a symbol table implemented as a std::map. For the value, there is no way to legitimately construct an instance of the value type via a default constructor. However if I don't provide a default constructor, I get a compiler error and if I make the constructor assert, my program compile just fine but crashes inside of map<K,V>::operator [] if I try to use it to add a new member.

Is there a way I can get C++ to disallow map[k] as an l-value at compile time (while allowing it as an r-value)?


BTW: I know I can insert into the map using Map.insert(map<K,V>::value_type(k,v)).


Edit: several people have proposed solution that amount to altering the type of the value so that the map can construct one without calling the default constructor. This has exactly the opposite result of what I want because it hides the error until later. If I were willing to have that, I could simply remove the assert from the constructor. What I Want is to make the error happen even sooner; at compile time. However, it seems that there is no way to distinguish between r-value and l-value uses of operator[] so it seems what I want can't be done so I'll just have to dispense with using it all together.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

晨曦慕雪 2024-08-22 15:59:05

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

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

m.insert( make_pair( k, v ) );

您也可以执行此操作。

m.emplace( k, v );
m.emplace( piecewise_construct, make_tuple(k), make_tuple(the_constructor_arg_of_v) );

在 C++11 中,即使未提供复制/移动构造函数,

You can't make the compiler differentiate between the two uses of operator[], because they are the same thing. Operator[] returns a reference, so the assignment version is just assigning to that reference.

Personally, I never use operator[] for maps for anything but quick and dirty demo code. Use insert() and find() instead. Note that the make_pair() function makes insert easier to use:

m.insert( make_pair( k, v ) );

In C++11, you can also do

m.emplace( k, v );
m.emplace( piecewise_construct, make_tuple(k), make_tuple(the_constructor_arg_of_v) );

even if the copy/move constructor is not supplied.

梦过后 2024-08-22 15:59:05

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

Use map<K,V>::at(). map<K,V>::operator [] will try to default-construct an element if the key provided does not already exist.

滥情空心 2024-08-22 15:59:05

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

A std::map > 确实有一个可默认构造的mapped_type,并且可能具有您想要的语义。请参阅 Boost.Optional 文档了解详细信息(您需要了解它们)。

Your V doesn't have a default constructor, so you cannot really expect std::map<K,V> std::map<K,V>::operator[] to be usable.

A std::map<K, boost::optional<V> > does have a mapped_type that is default-constructible, and likely has the semantics you want. Refer to the Boost.Optional documentation for details (you will need to be aware of them).

彼岸花似海 2024-08-22 15:59:05

如果值类型不可默认构造,那么 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;
    }
}

您也可以考虑使用 Python 中的 dict.get(key [, default]) 之类的 getter(如果 key 不存在,则返回提供的默认值(但这存在可用性问题,因为默认值)总是必须构造,即使您知道该密钥在地图中)。

If the value-type is not default-constructible, then operator[] just won't work for you.

What you can do, though, is to provide free functions that get and set values in a map for convenience.

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

You might also consider a getter like dict.get(key [, default]) in Python (which returns the provided default if key is not present (but that has a usability problem in that the default always has to be constructed, even if you know that key is in the map).

深府石板幽径 2024-08-22 15:59:05

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

Derive a new class from std::map<K,V> and create your own operator[]. Have it return a const reference, which can't be used as an l-value.

好菇凉咱不稀罕他 2024-08-22 15:59:05

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

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

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

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

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

同样,如果您想使用 STL 映射并且不愿意使用 insert 和 find 来代替运算符 [],那么这是一个解决方法。

It's a bit ugly, but one way to work around this is to add a member variable that tracks whether an instance is valid or not. Your default constructor would marks an instance as being invalid but all your other constructors mark the instance as valid.

Make sure your assignment operator properly transfers the new member variable.

Modify your destructor to ignore invalid instances.

Modify all your other member functions to throw/error/assert when they operate on an invalid instance.

You can then use your object in a map and as long as you only use objects that were properly constructed, your code will work fine.

Again, this is a workaround if you want to use the STL map and are not willing to use insert and find instead of operator[].

对你而言 2024-08-22 15:59:05

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

使用

map<K,V*>

而不是怎么样

map<K,V> ?

Not sure why it compiles for you, I think the compiler should have caught your missing constructor.

what about using

map<K,V*>

instead of

map<K,V> ?
ヅ她的身影、若隐若现 2024-08-22 15:59:05

您无法区分 operator[] 的左值和右值用法,因为它始终是左值表达式。如果您使用 [] 的替代方案,则不需要将 V 设为默认可构造。

对于查找,您可以使用 at,如果缺少键,它会抛出异常,而不是默认构造的。或者您可以使用 findlower_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);
    }
}

You can't distinguish between lvalue and rvalue uses of operator[], because it is always an lvalue expression. You don't need the V to be default constructible if you use alternatives to [].

For lookup, you can use at, which throws if the key is missing, rather than default-constructing one. Or you could use find, lower_bound or equal_range, which return iterators.

For assignment, you can use insert_or_assign if you have C++17, or write a free-function equivalent:

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);
    }
}
哑剧 2024-08-22 15:59:05

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

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

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

When you use an operator override in C++, it's best to stick as closely as possible with the semantics of the operator in the default case. The semantics of the default. operator[] is replacement of an existing member in an array. It would appear that std::map bends the rules a bit. That's unfortunate, because it leads to this sort of confusion.

Note that the documentation (http://www.sgi.com/tech/stl/Map.html) for operator[] under std::map says:"Returns a reference to the object that is associated with a particular key. If the map does not already contain such an object, operator[] inserts the default object data_type()."

I'd suggest that you treat replacement and insertion differently. Unfortunately, this means you need to know which is required. That may mean doing a lookup on the map first. If performance is an issue, you might need to find an optimization where you can test for membership and insert with one lookup.

微暖i 2024-08-22 15:59:05

您可以将 std::map 专门用于您的值类型。我并不是说这是一个好主意,但这是可以做到的。我将 scoped_ptr 的 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 值引用 (&&)。

you could specialize std::map for your value-type. I'm not saying it's a good idea, but it can be done. I specialized scoped_ptr<FILE>'s dtor to fclose instead of delete.

Something like:

 template<class K, class Compare, class Allocator>
 my_value_type& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
 {
   //...
 }

This should allow you to insert the code you want into operator[] for your type. Unfortunately, I do not know of a way in current c++ to return only r values. In c++0x you might be able to use:

 template<class K, class Compare, class Allocator>
 my_value_type&& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
 {
   //...
 }

This will return an R-value reference (&&).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文