当查看STL容器的成员函数时,我发现了一个奇怪的想法。为什么std::vector<T>::push_back(T)
之类的函数没有(可选)返回值(迭代器或甚至对附加对象的引用)?我知道std::string
函数如insert
和erase
返回迭代器,但这是显而易见的原因。我认为它经常会保存经常跟随这些函数调用的第二行代码。
我确信C ++的设计者有很好的理由,请赐教:)
UPDATE :我在这里包含了一个真实的代码示例,可以减少代码长度:
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 );
}
VS
if( m_token != "{" )
return new InnerState( m_targets.push_back( Dough(m_token) ), this );
答案 0 :(得分:6)
无法以安全的方式返回添加的元素或容器成员函数中的容器。 STL容器主要提供"strong guarantee"。返回操纵元素或容器将使得不可能提供强有力的保证(它只提供“基本保证”)。 这背后的原因是,返回一些东西可能会调用一个拷贝构造函数,这可能会抛出异常。但是这个功能已经退出,所以它成功地完成了它的主要任务,但仍然抛出异常,这违反了强有力的保证。你可能会想:“好吧,然后让我们通过引用返回!”虽然这听起来像是一个很好的解决方案,但它也不是非常安全。请考虑以下示例:
MyClass bar = myvector.push_back(functionReturningMyClass()); // imagine push_back returns MyClass&
但是,如果复制赋值运算符抛出,我们不知道push_back是否成功,从而间接违反了强保证。即使这不是直接违规。当然使用MyClass& bar = //...
可以解决这个问题,但是容器可能会进入不确定状态会非常不方便,因为有人忘了&
。
一个非常相似的reasoning是std::stack::pop()
不返回弹出值的事实。相反,top()
以安全的方式返回最顶层的值。在调用top之后,即使复制构造函数或复制赋值构造函数抛出,您仍然知道堆栈没有改变。
编辑: 我相信为新添加的元素返回一个迭代器应该是完全安全的,如果iterator-type的copy-constructor提供了无抛出保证(并且我知道的每一个都有)。
答案 1 :(得分:5)
有趣的问题。显而易见的返回值将是操作发生的向量(或其他),因此您可以编写如下代码:
if ( v.push_back(42).size() > n ) {
// do something
}
我个人不喜欢这种风格,但我想不出一个不支持它的好理由。
答案 2 :(得分:3)
因为.back()会立即为你返回吗?
从概念上讲,C ++设计人员不会在成员函数中实现任何难以或不可能在公共接口中实现的功能。调用.back()简单易行。对于迭代器,您可以(结束 - 1)或仅auto it = end; it--;
标准委员会使新代码成为可能,并大量简化了常用的代码。像这样的东西不在要做的事情列表中。
答案 3 :(得分:2)
v.insert(v.end(),x);
与返回迭代器的push_back相当。为什么push_back本身不会返回迭代器超出我的范围。
答案 4 :(得分:0)
我认为它与返回值的概念有关: 返回值不是为了您的方便而是为了“计算”的概念结果,他们显然认为push_back在概念上不会产生任何结果。
答案 5 :(得分:0)
我不确定,但我认为变异std::string
成员返回iterator
的原因之一是程序员可以获得{{1}的非常量迭代器在变异操作之后,不需要第二次“泄漏”。
std::string
接口旨在支持名为copy-on-write的模式,这基本上意味着任何变异操作都不会影响原始数据,而是影响副本。例如,如果您使用字符串std::basic_string
并将"abcde"
替换为'a'
来获取'z'
,则结果字符串的数据可能会占用堆中的不同位置而不是原始字符串的数据。
如果获得"zbcde"
的非常量迭代器,则COW字符串实现必须复制(也称为“泄漏原始”)。否则,程序可以更改基础数据(并违反只读不变量)with:
std::string
但是,在字符串变换操作之后,生成的字符串对象已经拥有数据的唯一所有权,因此字符串实现不需要再次泄漏其数据以生成非常量迭代器。
char& c0 = *str.begin();
c0 = 'z';
是不同的,因为它不支持写时复制语义。
注意:我从std::vector
的libstdc ++实现中得到了术语 leak 。此外,“泄漏数据”并不意味着实施leaks memory。
编辑:以下是std::basic_string
的libstdc ++定义供参考:
std::basic_string<CharT, Traits, Alloc>::begin()
答案 6 :(得分:0)
也许是因为它不是“需要”的?
erase()
和insert()
没有其他方法可以返回迭代器以允许继续调用它。
我认为没有充分的理由支持push_back()
的相同逻辑。
但是可以肯定的是,制作更加神秘的表达方式会很精彩。 (我没有看到你的例子有所改进,看起来这是在阅读你的代码时放慢你的同事的好方法......)
答案 7 :(得分:-2)
不确定他们是否有充分的理由,但这个功能已经足够慢了。