为什么 std::map::operator[] 如此违反直觉?

发布于 2024-10-06 16:43:26 字数 257 浏览 2 评论 0原文

在我看来,通常用于访问数据结构的运算符突然被定义为将数据插入到数据结构中,这似乎是“邪恶的”(在 C++ FAQ 中)。

我想问题是“什么会更好”?对于某些类型的映射值,这个问题很容易回答;例如,如果我们将键映射到指针,您可能真的喜欢operator[]为不存在的键返回nullptr,但这显然不适用于其他类型。

它可能会在不存在的键上抛出异常,甚至默认构造一个临时值并返回它而不将其添加到映射中。对于这种容器类型,将 [] 从读取语义转变为写入语义的充分理由是什么?

It seems to me 'evil' (in the C++ FAQ sense of the word), for an operator which is generally used to access a data structure to suddenly be defined to insert data into a data structure.

I guess the issue is 'what would be better'? This question is answered easily for certain types of mapped value; for example, if we map keys to pointers, you might really like operator[] to return nullptr for a non-existent key, but that clearly doesn't work for other types.

It could throw an exception on non-existent key, or even default construct a temporary and return that without adding it to the map. What is the good reason for turning [] from read semantics to write semantics for this container type?

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

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

发布评论

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

评论(4

小傻瓜 2024-10-13 16:43:26

基本问题是没有语法方法可以可靠地区分:

dosomething(collection[foo]);

from

collection[foo] = something;

运算符的定义。因为它可能出现在任一位置,所以该类确保它可以处理这两个位置,并在必要时提供默认覆盖。如果您发现这是不合理的,那么您需要完全避免 std::map::operator[]

另一个原因是当键不在列表中时必须有一些定义的行为。由于operator[]必须返回一个值(LValue或RValue),因此它不能返回空指针、尾后迭代器或任何其他标记值。唯一剩下的选择是提出例外。 STL 不会引发太多异常,因为即使在没有异常的情况下也可以使用它。必须选择其他一些行为,这就是结果。

解决此问题的最佳方法是使用不具有此行为的 std::map 成员函数。这将是 map::find(),如果未找到该键,它会返回 map::end

The basic problem is that there is no syntactic way to reliably distinguish:

dosomething(collection[foo]);

from

collection[foo] = something;

in the operator's definition. Because it may appear in either location, the class makes sure that it can handle both, providing a default to overwrite, if necessary. If you find this to be unconscionable, then you need to avoid std::map::operator[] altogether.

Another reason for this is there must be some defined behavior for when the key is not in the list. Since operator[] must return a value (either LValue or RValue), then it cannot return a null pointer, past-the-end iterator, or any other sentinel value. The only remaining option would be to raise an exception. The STL doesn't raise very many exceptions, because it is intended to be used even in cases where exceptions are not. Some other behavior must be chosen, and this is the result.

The best way around this is to use a member function of std::map that doesn't have this behavior. That would be map::find(), which returns map::end if the key is not found.

新人笑 2024-10-13 16:43:26

“对于这种容器类型,将 [] 从读取语义转向写入语义的充分理由是什么?”

想了一会儿,我想到了两个原因。第一个原因是效率。它有助于反思实际的算法以及语义是否使生活变得更容易或更困难。当前语义中最突出的一种算法是累积与键相关的值。

void f(std::vector<std::pair<std::string, double> > const& v)
{
        std::map<std::string, double> count;

        for (size_t i = 0, sz = v.size(); i < sz; ++i) {
                count[v[i].first] += v[i].second;
        }
}

在这种情况下,映射语义很好,因为您可以依赖将 count 中的每个值初始化为零,这可能就是您想要的。在这种情况下,我们只对每个键和值对在映射中进行一次搜索。

如果你将其与 Python 进行比较(如果按照你的建议,如果键不存在,它会抛出异常),你会得到更混乱且效率更低的代码,如下所示:

def f(vec):
        count = {}
        for (k, v) in vec:
                if count.has_key(k):
                        count[k] += v
                else:
                        count[k] = v

或者使用 get() 和默认值的稍微简洁的版本。

def g(vec):
        count = {}
        for (k, v) in vec:
                count[k] = count.get(k, 0) + v
        return count

请注意,在这两个版本中,都会对每个键和值对执行两次字典搜索。根据您的要求,这可能是严厉的处罚。因此,在这种情况下,C++ 映射语义对于高效代码是必要的。

C++ 有 const,它是保护事物不被改变的绝佳工具。我有时怀疑 const 被严重低估了。在您的情况下,使用 const 将防止您使用运算符 [] 更改地图的内容。

这种行为的第二个充分理由是它与许多语言中关联数组的行为相同。几十年来,像 Awk 和 Perl 这样的语言对于关联数组都具有相同的行为。如果您来自这些语言,std::map 的行为可能非常直观。

"What is the good reason for turning [] from read semantics to write semantics for this container type?"

Having thought about it for a bit longer I can think of two reasons. The first reason is efficiency. It helps to reflect on actual algorithms and whether the semantics make life easier or harder. One algorithms where the current semantics shine is in accumulating values associated with keys.

void f(std::vector<std::pair<std::string, double> > const& v)
{
        std::map<std::string, double> count;

        for (size_t i = 0, sz = v.size(); i < sz; ++i) {
                count[v[i].first] += v[i].second;
        }
}

The map semantics are nice in this case because you can rely on each value in count being initialised with zero which is likely to be what you want. In this case we only do one search into the map for each key and value pair.

If you compare that with Python (which throws an exception if the key is absent as you suggest), you get messier and less efficient code that looks like:

def f(vec):
        count = {}
        for (k, v) in vec:
                if count.has_key(k):
                        count[k] += v
                else:
                        count[k] = v

Or a slightly neater version using get() and default values.

def g(vec):
        count = {}
        for (k, v) in vec:
                count[k] = count.get(k, 0) + v
        return count

Note that in both these version two searches into the dictionary are performed for each key and value pair. Which can be a severe penalty depending on your requirements. So the C++ map semantics are necessary for efficient code in this case.

C++ has const which is a wonderful facility for protecting things from changing. I sometimes suspect that const is massively under-appreciated. In your case using const will protect you from changing the contents of your map using operator[].

The second good reason for this behaviour is that it is the same as the behaviour of associative arrays in a number of languages. Languages like Awk and Perl have had the same behaviour for associative arrays for decades. If you are coming from these languages, the behaviour of std::map is probably very intuitive.

后知后觉 2024-10-13 16:43:26

正如 TokenMacGuy 提到的, ::operator[] 的用法可能不明确,您所描述的是他们处理不明确性的方式。

作为软件开发人员需要注意的一件重要事情是,第 3 方库几乎从来都不是按照您使用它们的方式编写的。更糟糕的是,设计不当的第三方库可能会损害代码的质量。

您刚才描述的所有内容都可以通过抽象化 std::map 类来轻松完成,如果 ::operator[] 的副作用让您感到那么困扰,我会鼓励您将其抽象化。

As TokenMacGuy mentioned, the usage of the ::operator[] can be ambiguous, and what you described was their way of handling the ambiguity.

An important thing to look at as a software developer is that 3rd party libraries are almost never written exactly the way you would use them. Even worse, poorly designed 3rd party libraries can damage the quality of your code.

Everything you just described can be easily accomplished by abstracting the std::map class away, and if the side effects of ::operator[] bother you that much, I would encourage that you abstract it away.

且行且努力 2024-10-13 16:43:26

要了解有关 STL 的大多数设计决策,需要了解其演变,尤其是 SGI STL< /a>.

mapPair 关联容器 的模型以及唯一排序关联容器

第二个概念很重要,因为它与 set 共享,而通过 key 进行访问(使用 operator[])是没有意义的。

insert 的语义(不替换值)被设计为适合 mapset (使得分解它们的实现成为可能) 。请注意,对于元素不可变的 set 来说,替换也没有意义。

因此,我相信,在引入 operator[] 时,选择了这种奇怪的语义,以便您可以为 map 提供 insert 的替代方案和 multimap,而不是稍微复杂一点的:

map.insert(std::make_pair(key, Value())).first->second = value;

老实说,我希望它的行为更像 find (带有 std::key_error)。这种语义当然不直观。

To understand most design decisions about the STL, one needs to look at its evolution, and notably at the SGI STL.

A map is a model of an Pair Associative Container and of a Unique Sorted Associative Container.

The second concept is important because it is shared with set, for which an access by key (using operator[]) just does not makes sense.

The semantics of insert (which does not replace the value) were designed to fit both map and set (making it possible to factorize their implementation). Note that for a set, whose elements are immutable, replacement does not makes sense either.

It is my belief therefore, than when introducing operator[], this weird semantic was chosen so that you would have an alternative to insert for map and multimap, instead of the slightly more complicated:

map.insert(std::make_pair(key, Value())).first->second = value;

Honestly, I would have a preferred it to behave more like find (with a std::key_error). This semantic certainly isn't intuitive.

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