简体   繁体   English

STL容器函数返回值

[英]STL container function return values

When looking over the member functions of the STL containers, an odd thought occurred to me. 在查看STL容器的成员函数时,我发现了一个奇怪的想法。 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)? 为什么std::vector<T>::push_back(T)类的函数没有(可选)返回值(迭代器甚至是对附加对象的引用)? I know std::string functions like insert and erase return iterators, but that's for obvious reasons. 我知道std::string函数像inserterase返回迭代器,但这是显而易见的原因。 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 :) 我敢肯定C ++的设计师有很好的理由,请赐教:)

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. 如果我假设std::list::push_back返回对添加元素的引用。 The code is a bit heavy, but that's mostly (two sets of parentheses) due to unique_ptr 's constructor and dereferencing it. 代码有点沉重,但由于unique_ptr的构造函数并且取消引用它,这主要是(两组括号)。 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 );

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" . STL容器大多提供“强有力的保证” 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. 尽管如此,如果复制赋值运算符抛出,我们也不知道push_back是否成功,从而间接违反了强保证。 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 & . 当然使用MyClass& bar = //...而不是解决这个问题,但是它很不方便,容器可能会进入一个不确定的状态,只是因为有人忘记了&

A quite similar reasoning is behind the fact that std::stack::pop() does not return the popped value. 一个非常相似的推理背后的事实是std::stack::pop()没有返回弹出的值。 Instead top() returns the topmost value in a safe way. 相反, top()以安全的方式返回最顶层的值。 after calling top, even when a copy-constructor, or a copy-assignment constructor throws, you still know that the stack is unchanged. 在调用top之后,即使复制构造函数或复制赋值构造函数抛出,您仍然知道堆栈没有改变。

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). 编辑:我相信为新添加的元素返回一个迭代器应该是完全安全的,如果迭代器类型的复制构造函数提供无抛出保证(我知道的每一个)。

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. 我个人不喜欢这种风格,但我想不出一个不支持它的好理由。

Because there's .back() that will instantly return it for you? 因为.back()会立即为你返回吗?

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. 从概念上讲,C ++设计人员不会在成员函数中实现任何难以或不可能在公共接口中实现的功能。 Calling .back() is simple and easy enough. 调用.back()简单易行。 For an iterator, you could do (end - 1) or just auto it = end; it--; 对于迭代器,你可以做(​​结束 - 1)或只是auto it = end; it--; 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. 像这样的东西不在要做的事情列表中。

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

Would be equivalent to push_back with returning an iterator. 将等效于返回迭代器的push_back。 Why push_back itself doesn't return an iterator is beyond me. 为什么push_back本身不返回迭代器是超出我的。

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

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". 我不确定,但我认为变异的std::string成员返回iterator的原因之一是程序员可以在变异操作后获得std::string的非常量迭代器,而不需要第二次“泄漏”。

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. std::basic_string接口旨在支持一种名为copy-on-write的模式 ,这基本上意味着任何变异操作都不会影响原始数据,而是一个副本。 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. 例如,如果您使用字符串"abcde"并将'a'替换为'z'以获取"zbcde" ,则结果字符串的数据可能会占用堆中与原始字符串数据不同的位置。

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"). 如果获得std::string的非常量迭代器,那么COW字符串实现必须复制(也称为“泄漏原始”)。 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. std::vector是不同的,因为它不支持copy-on-write语义。

Note: I got the term leak from the libstdc++ implementation of std::basic_string . 注意:我从std::basic_string的libstdc ++实现中得到了术语泄漏 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: 编辑:这是std::basic_string<CharT, Traits, Alloc>::begin()的libstdc ++定义供参考:

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

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. erase()insert()没有别的办法,只能返回迭代器以允许继续调用它的循环。

I don't see a good reason to support the same logic with push_back() . 我没有看到使用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...) (我没有看到你的例子有所改进,它看起来是一个在阅读你的代码时让你的同事慢下来的好方法......)

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM