【问题标题】:STL container function return valuesSTL 容器函数返回值
【发布时间】:2010-07-04 19:42:44
【问题描述】:

查看 STL 容器的成员函数时,我想到了一个奇怪的想法。为什么像std::vector<T>::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 );

【问题讨论】:

    标签: c++ stl return-value containers


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 哇。很好的信息,谢谢!仍然让我想知道为什么他们当然没有返回迭代器:)。也许是因为您可以 MyClass bar = *(myvector.push_back(functionReturningMyClass())); 并且可能遇到与返回引用相同的问题(或不返回引用?)。
    • 我猜,原因是性能问题,因为这需要构造一个迭代器,并复制该迭代器,如果不使用返回值,那将是相当大的开销。
    • 不会有任何像样的编译器优化这样的事情吗?可能不是标准委员会所关注的:)
    【解决方案2】:

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

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

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

    【讨论】:

    • 我在element = v.push_back(i); element.somefunction(someVariable); 的行中考虑得更多,但你的当然也可以。
    【解决方案3】:

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

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

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

    【讨论】:

    • 精度:在end上调用--之前确保容器中有元素。否则它是未定义的行为。
    • 这与.back() 的限制相同,并且是 push_back() 成功的后置条件(即不抛出)
    【解决方案4】:
    v.insert(v.end(),x);
    

    相当于 push_back 返回一个迭代器。为什么 push_back 本身不返回迭代器超出了我的理解。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

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

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

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

        但是,在字符串突变操作之后,生成的字符串对象已经拥有数据的唯一所有权,因此字符串实现不需要第二次泄漏其数据来制作非 const 迭代器。

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

        注意:我从 std::basic_string 的 libstdc++ 实现中得到了术语 leak。还有,“泄露数据”并不意味着实现leaks memory

        编辑:这是std::basic_string&lt;CharT, Traits, Alloc&gt;::begin()的libstdc++定义供参考:

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

        【讨论】:

          【解决方案7】:

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

          erase()insert() 只能返回一个迭代器以允许继续调用它的循环。

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

          但可以肯定的是,制作更神秘的表达方式会很棒。 (我没有看到您的示例有任何改进,这似乎是在阅读您的代码时减慢同事速度的好方法......)

          【讨论】:

            【解决方案8】:

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

            【讨论】:

            • 怎么,“慢”?返回值会有什么不同?
            • 由于返回值主要在某个寄存器中返回,实际上它有可能已经默默地返回了指向插入元素的指针。而且它不会有太大的不同,因为 vector::back() 是恒定的,所以迭代器(或指针)必须由向量保存。
            猜你喜欢
            • 2021-12-09
            • 1970-01-01
            • 2012-12-24
            • 1970-01-01
            • 2013-04-03
            • 1970-01-01
            • 1970-01-01
            • 2010-12-19
            • 1970-01-01
            相关资源
            最近更新 更多