STL容器函数返回值

发布于 2024-09-08 00:45:00 字数 1059 浏览 1 评论 0原文

当查看 STL 容器的成员函数时,我想到了一个奇怪的想法。为什么像 std::vector::push_back(T) 这样的函数没有(可选)返回值(迭代器甚至对附加对象的引用)?我知道 std::string 函数如 inserterase 返回迭代器,但这是出于显而易见的原因。我认为它通常会保存这些函数调用之后的第二行代码。

我确信 C++ 的设计者有一个很好的理由,请赐教:)

更新:我在这里包含了一个真实世界的代码示例,它可以减少代码长度:

if( m_token != "{" )
{
    m_targets.push_back( unique_ptr<Target>(new Dough(m_token)) );
    return new InnerState( *(m_targets.back()), this );
}

可以减少到

if( m_token != "{" )
    return new InnerState( *(m_targets.push_back( unique_ptr<Target>(new Dough(m_token)) )), this );

如果我假设 std::list::push_back 返回对添加元素的引用。代码有点重,但这主要是(两组括号)由于 unique_ptr 的构造函数和取消引用它造成的。也许为了清楚起见,没有任何指针的版本:

if( m_token != "{" )
{
    m_targets.push_back( Dough(m_token) );
    return new InnerState( m_targets.back(), this );
}

与。

if( m_token != "{" )
    return new InnerState( m_targets.push_back( Dough(m_token) ), this );

When looking over the member functions of the STL containers, an odd thought occurred to me. Why don't functions like std::vector<T>::push_back(T) not have an (optional) return value (iterator or even a reference to the appended object)? I know std::string functions like insert and erase return iterators, but that's for obvious reasons. I'd think it'd often save a second line of code that often follows these function calls.

I'm sure the designers of C++ have a very good reason, please enlighten me :)

UPDATE: I'm including a real-world code example here where it could reduce code length:

if( m_token != "{" )
{
    m_targets.push_back( unique_ptr<Target>(new Dough(m_token)) );
    return new InnerState( *(m_targets.back()), this );
}

could be reduced to

if( m_token != "{" )
    return new InnerState( *(m_targets.push_back( unique_ptr<Target>(new Dough(m_token)) )), this );

If I assume std::list::push_back returns a reference to the added element. The code is a bit heavy, but that's mostly (two sets of parentheses) due to unique_ptr's constructor and dereferencing it. Perhaps for clarity a version without any pointers:

if( m_token != "{" )
{
    m_targets.push_back( Dough(m_token) );
    return new InnerState( m_targets.back(), this );
}

vs.

if( m_token != "{" )
    return new InnerState( m_targets.push_back( Dough(m_token) ), this );

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

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

发布评论

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

评论(8

2024-09-15 00:45:00

不可能以安全的方式返回添加的元素或容器成员函数中的容器。 STL容器主要提供“强有力的保证”。返回被操作的元素或容器将无法提供强保证(它只会提供“基本保证”)。
这背后的原因是,返回某些内容可能会调用复制构造函数,这可能会引发异常。但是该函数已经退出,因此它成功地完成了其主要任务,但仍然抛出异常,这违反了强保证。您可能会想:“那么让我们通过引用返回!”,虽然这听起来像是一个很好的解决方案,但它也不是完全安全的。考虑下面的例子:

MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass&

仍然,如果复制赋值运算符抛出异常,我们不知道push_back是否成功,从而间接违反了强保证。尽管这并不是直接违规。当然使用 MyClass& bar = //... 相反可以解决这个问题,但这会很不方便,容器可能会进入不确定状态,只是因为有人忘记了 &

一个非常相似的推理背后的事实是std::stack::pop() 不返回弹出的值。相反,top() 以安全的方式返回最上面的值。调用 top 后,即使复制构造函数或复制赋值构造函数抛出异常,您仍然知道堆栈没有更改。

编辑:
我相信,如果迭代器类型的复制构造函数提供不抛出保证(我所知道的每个人都这样做),则为新添加的元素返回迭代器应该是完全安全的。

Returning the added element, or the container in container member functions is not possible in a safe way. STL containers mostly provide the "strong guarantee". Returning the manipulated element or the container would make it impossible to provide the strong guarantee (it would only provide the "basic guarantee").
The reason behind this is, that returning something could possibly invoke an copy-constructor, which may throw an exception. But the function already exited, so it fulfilled its main task successfully, but still threw an exception, which is a violation of the strong guarantee. You maybe think: "Well then lets return by reference!", while this sounds like a good solution, its not perfectly safe either. Consider following example:

MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass&

Still, if the copy-assignment operator throws, we dont know if push_back succeded or not, thus indirectly violating the strong-guarantee. Even though this is not a direct violation. Of course using MyClass& bar = //... instead would fix this issue, but it would be quite inconvenient, that a container might get into an indeterminate state, just because someone forgot a &.

A quite similar reasoning is behind the fact that std::stack::pop() does not return the popped value. Instead top() returns the topmost value in a safe way. after calling top, even when a copy-constructor, or a copy-assignment constructor throws, you still know that the stack is unchanged.

EDIT:
I believe returning an iterator for the newly added element should be perfectly safe, if the copy-constructor of the iterator-type provides the no-throw guarantee (and every i know of does).

梦明 2024-09-15 00:45:00

有趣的问题。明显的返回值将是进行操作的向量(或其他值),因此您可以编写如下代码:

if ( v.push_back(42).size() > n ) {
   // do something
}

我个人不喜欢这种风格,但我想不出不支持它的充分理由。

Interesting question. The obvious return value would be the vector (or whatever) that the operation takes place on, so you could then write code like:

if ( v.push_back(42).size() > n ) {
   // do something
}

I personally don't like this style, but I can't think of a good reason not to support it.

三生路 2024-09-15 00:45:00

因为 .back() 会立即为你返回它?

从概念上讲,C++ 设计者不会在成员函数中实现任何在公共接口中难以或不可能实现的内容。调用 .back() 非常简单。对于迭代器,您可以执行 (end - 1) 或只是 auto it = end; it--;

标准委员会使新代码成为可能,并大大简化了非常常用的代码。像这样的事情不在要做的事情清单上。

Because there's .back() that will instantly return it for you?

Conceptually, the C++ designers won't implement in a member function anything that would be difficult or impossible for you to implement in a public interface. Calling .back() is simple and easy enough. For an iterator, you could do (end - 1) or just auto it = end; it--;

The Standards committee makes new code possible and massively simplifies code that is very commonly used. Stuff like this just isn't on the list of things to do.

知你几分 2024-09-15 00:45:00
v.insert(v.end(),x);

相当于返回迭代器的push_back。为什么 Push_back 本身不返回迭代器超出了我的范围。

v.insert(v.end(),x);

Would be equivalent to push_back with returning an iterator. Why push_back itself doesn't return an iterator is beyond me.

心病无药医 2024-09-15 00:45:00

我认为这与返回值的概念有关:
返回值的存在不是为了您的方便,而是为了“计算”的概念结果,他们显然认为 Push_back 在概念上不会产生任何结果。

I think it has to do with the concept of a return value:
the return value is there not for your convenience but for a conceptual result of the 'computation' they apparently thought push_back conceptually doesn't result in anything.

安静被遗忘 2024-09-15 00:45:00

我不确定,但我认为变异 std::string 成员返回 iterator 的原因之一是程序员可以获得非常量 -变异操作后指向 std::string 的迭代器,无需第二次“泄漏”。

std::basic_string 接口旨在支持名为 的模式写入时复制,这基本上意味着任何变异操作不会影响原始数据,而是影响副本。例如,如果您有字符串 "abcde" 并将 'a' 替换为 'z' 以获得 "zbcde"< /code>,结果字符串的数据可能与原始字符串的数据占据堆中不同的位置。

如果您获得 std::string 的非常量迭代器,那么 COW 字符串实现必须制作一个副本(也称为“泄漏原始版本”)。否则,程序可以更改底层数据(并违反只读不变式)with

char& c0 = *str.begin();
c0 = 'z';

但是,在字符串变异操作,生成的字符串对象已经拥有数据的唯一所有权,因此字符串实现不需要再次泄漏其数据来创建非常量迭代器。

std::vector 是不同的,因为它不支持写时复制语义。

注意:我从 std::basic_string 的 libstdc++ 实现中得到了术语leak。此外,“泄漏数据”并不意味着实现泄漏内存

编辑:以下是 std::basic_string::begin() 的 libstdc++ 定义供参考:

iterator
begin()
{
    _M_leak();
    return iterator(_M_data());
}

I am not sure, but I think that one of the reasons why the mutating std::string members return an iterator is so that the programmer could obtain a non-const-iterator to the std::string after a mutating operation without requiring a second "leak".

The std::basic_string interface was designed to support a pattern called copy-on-write, which basically means that any mutating operation does not affect the original data, but a copy. For example, if you had the string "abcde" and replaced 'a' with 'z' to get "zbcde", the data for the resulting string might occupy a different location in the heap than the data for the original string.

If you obtain a non-const-iterator of a std::string, then a COW string implementation must make a copy (also called "leak the original"). Otherwise, the program can change the underlying data (and violate the read-only invariant) with:

char& c0 = *str.begin();
c0 = 'z';

But, after a string mutation operation, the resulting string object already has sole ownership of the data, so the string implementation does not need to leak its data a second time to make a non-const-iterator.

A std::vector is different because it does not support copy-on-write semantics.

Note: I got the term leak from the libstdc++ implementation of std::basic_string. Also, "leaking the data" does not mean that the implementation leaks memory.

EDIT: Here is the libstdc++ definition of std::basic_string<CharT, Traits, Alloc>::begin() for reference:

iterator
begin()
{
    _M_leak();
    return iterator(_M_data());
}
伴梦长久 2024-09-15 00:45:00

也许是因为它不是“需要”的?

erase()insert() 除了返回迭代器以允许继续调用它的循环之外没有其他方法。

我认为没有充分的理由支持与 push_back() 的逻辑相同。

但当然,如果能做出更神秘的表达那就太好了。 (我没有看到你的例子有任何改进,这看起来是一个在阅读你的代码时减慢你的同事速度的好方法......)

Maybe because it was not "needed"?

erase() and insert() have no other way than return an iterator to allow continuing a loop it was called in.

I don't see a good reason to support the same logic with push_back().

But sure, it would be be wonderful to make more cryptic expressions. (I don't see an improvement in your example, it looks like a good way to slow your coworkers when reading your code...)

薄暮涼年 2024-09-15 00:45:00

不确定他们有充分的理由,但这个功能已经足够慢了。

Not sure they had a very good reason, but this function is slow enough already.

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