2010-07-04 101 views
7

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

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

更新:我这里有包括现实世界中的代码示例它可以减少代码长度:

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

如果我假定std::list::push_back返回到所添加的元素的引用可以减少到

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

。该代码有点沉重,但主要是(两组括号),由于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); 

回答

5

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

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

不过,如果复制赋值操作符抛出,我们不知道的push_back succeded与否,从而间接地违反了强有力的担保。尽管这不是直接违反。当然,使用MyClass& bar = //...可以解决这个问题,但是如果有人忘记了&,容器可能会进入不确定状态,这会非常不方便。

一个非常相似的reasoning是因为std::stack::pop()不返回弹出的值。而是top()以安全的方式返回最高值。在调用top之后,即使在复制构造函数或复制赋值构造函数抛出时,您仍然知道堆栈没有变化。

编辑: 我相信回国为新加入的元素的迭代器应该是绝对安全的,如果迭代式的拷贝构造函数提供了无抛出保证(和每一个我所知道的一样)。

+0

哇。很棒的信息,谢谢!仍然让我想知道为什么他们没有返回一个迭代器当然:)。也许是因为你可以'MyClass bar = *(myvector.push_back(functionReturningMyClass()));'并且可能与返回引用(或不是?)相同。 – rubenvb 2010-07-11 18:51:13

+0

我想,原因是性能问题,因为那件事需要构造一个迭代器,并且复制那个迭代器,如果没有使用返回值,那么这是一个开销。 – smerlin 2010-07-11 19:01:58

+0

会不会有任何体面的编译器优化这样的事情?可能不是什么标准委员会在看:) – rubenvb 2010-07-11 20:16:39

-2

不知道他们有一个很好的理由,但这个功能是够慢的了。

+4

怎么这么“慢”?回报值会有什么不同? – 2010-07-04 19:51:39

+0

因为返回值大部分是在某个寄存器中返回的,实际上它有一个机会,它已经静静地返回一个指向插入元素的指针。而且它不会有太大的区别,因为矢量 :: back()是常量,所以迭代器(或指针)必须由矢量保存。 – flownt 2010-07-04 19:55:24

+3

比较慢什么? – GManNickG 2010-07-04 20:08:31

5

有趣的问题。最明显的返回值将是操作发生在载体(或其他),这样的话你可以写类似的代码:

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

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

+0

我正在考虑'element = v.push_back(i); element.somefunction(someVariable);',但你的工作当然也会如此。 – rubenvb 2010-07-04 19:58:35

0

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

3

因为有.back()会立即返回给你吗?

从概念上讲,C++设计人员不会在成员函数中实现任何在公共接口中难以或不可能实现的东西。调用.back()很简单,很简单。对于一个迭代器,你可以做(​​结束 - 1)或者仅仅是auto it = end; it--;

标准委员会使得新的代码成为可能并且大量地简化了非常常用的代码。像这样的东西只是不在要做的事情列表中。

+0

精度:在调用' - end'上的'--'前确保容器中有元素。否则它是未定义的行为。 – 2010-07-05 06:18:36

+0

这与'.back()'具有相同的限制,并且push_back()后继条件成功(即不抛出) – MSalters 2010-07-05 08:21:13

0

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

std::basic_string接口被设计为支持称为copy-on-write的模式,这基本上意味着任何变异操作不会影响原始数据,而是副本。例如,如果您使用字符串"abcde"并用'z'替换'a'以得到"zbcde",则结果字符串的数据可能会在堆中占据与原始字符串数据不同的位置。

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

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

但是,一个字符串变异操作后,得到的字符串对象已有数据的独家所有权,因此字符串实现不需要第二次泄漏其数据来创建非常量迭代器。

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

注意:我从的libstdC++实现中获得了漏洞的术语。另外,“泄漏数据”并不意味着执行leaks memory

编辑:下面是参考的std::basic_string<CharT, Traits, Alloc>::begin()所述的libstdC++定义:

iterator 
begin() 
{ 
    _M_leak(); 
    return iterator(_M_data()); 
} 
2
v.insert(v.end(),x); 

将相当于与返回的一个迭代的push_back。为什么push_back本身不会返回迭代器超出了我的想象。

0

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

erase()insert()比返回一个迭代器,允许继续循环它被称为在没有其他办法。

我没有看到一个很好的理由来支持相同的逻辑与push_back()

但是,当然,制作更多神秘的表情将是美好的。 (我没有看到你的例子有所改进,它看起来像是一个很好的方式来减慢你的同事在阅读你的代码时...)