代码之家  ›  专栏  ›  技术社区  ›  rubenvb

STL容器函数返回值

  •  8
  • rubenvb  · 技术社区  · 14 年前

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

    我确信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 );
    }
    

    VS

    if( m_token != "{" )
        return new InnerState( m_targets.push_back( Dough(m_token) ), this );
    
    8 回复  |  直到 14 年前
        1
  •  6
  •   smerlin    14 年前

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

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

    但是,如果拷贝分配操作符抛出,我们就不知道push-back是否成功,从而间接违反了强保证。即使这不是直接违反。当然使用 MyClass& bar = //... 相反,它会解决这个问题,但这将非常不方便,容器可能会进入不确定状态,只是因为有人忘记了 & .

    相当相似 reasoning 背后的事实是 std::stack::pop() 不返回弹出值。相反 top() 以安全的方式返回最上面的值。调用top之后,即使复制构造函数或复制分配构造函数抛出,您仍然知道堆栈是不变的。

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

        2
  •  5
  •   anon    14 年前

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

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

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

        3
  •  3
  •   Puppy    14 年前

    因为有.back()可以立即为您返回?

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

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

        4
  •  2
  •   5ound    14 年前
    v.insert(v.end(),x);
    

    相当于通过返回迭代器将\推回。为什么push-back本身不返回迭代器超出了我的能力。

        5
  •  0
  •   flownt    14 年前

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

        6
  •  0
  •   Daniel Trebbien    14 年前

    我不确定,但我认为变异的原因之一 std::string 成员返回 iterator 这样程序员就可以获得一个非常量迭代器 STD::字符串 在不需要第二次“泄漏”的情况下进行突变操作后。

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

    如果您获得一个非常量迭代器 STD::字符串 ,然后是一个cow字符串实现 必须 复制(也称为“泄露原件”)。否则,程序可以更改基础数据(并违反只读不变量) with :

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

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

    std::vector 是不同的,因为它不支持copy-on-write语义。

    注:我有这个术语 泄漏 从libstdc++实现 std::基本_字符串 . 另外,“泄露数据”并不意味着实现 leaks memory .

    编辑: 以下是libstdc++的定义 std::basic_string<CharT, Traits, Alloc>::begin() 供参考:

    iterator
    begin()
    {
        _M_leak();
        return iterator(_M_data());
    }
    
        7
  •  0
  •   Klaim    14 年前

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

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

    我看不出一个很好的理由来支持同样的逻辑 push_back() .

    当然,做更多的神秘表达会很好。(我看不到您的示例有什么改进,这看起来是一个很好的方法,可以让您的同事在阅读代码时放慢速度…)

        8
  •  -2
  •   Pavel Radzivilovsky    14 年前

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