返回一堆值的接口

时间:2013-05-09 17:19:21

标签: c++ c++11 c++98

我有一个函数,它接受一个数字并返回那么多东西(比方说,整数)。什么是最干净的界面?一些想法:

  1. 返回vector<int>。矢量将被复制多次,效率很低。
  2. 返回vector<int>*。我的getter现在必须分配向量本身以及元素。有谁必须释放向量的所有常见问题,你不能分配一次并使用相同的存储器来进行对getter的许多不同调用等。这就是为什么STL算法通常避免分配内存,而是想要它传了进来。
  3. 返回unique_ptr<vector<int>>。现在很清楚是谁删除了它,但我们还有其他问题。
  4. vector<int>作为参考参数。 getter可以push_back(),调用者可以决定是否reserve()空间。但是,如果传入的vector非空,那么getter应该怎么做?附加?通过先清除它来覆盖?断言它是空的?如果函数的签名只允许一次解释,那就太好了。
  5. 传递beginend迭代器。现在我们需要返回实际写入的项目数(可能小于预期),并且调用者需要注意不要访问从未写入的项目。
  6. 让getter取iterator,来电者可以通过insert_iterator
  7. 放弃并传递char *。 :)

4 个答案:

答案 0 :(得分:37)

在C ++ 11中,标准容器支持移动语义,应该使用选项1

它使你的函数的签名清晰,传达你只想要返回一个整数向量,并且它将是高效的,因为不会发出副本:std::vector的移动构造函数将被调用(或者,最有可能的是,将应用命名返回值优化,导致无移动且无副本):

std::vector<int> foo()
{
    std::vector<int> v;
    // Fill in v...
    return v;
}

通过这种方式,您不必处理诸如所有权,不必要的动态分配以及其他只是污染问题简单性的问题:返回一堆整数。

在C ++ 03中,可能想要使用选项4并对非const向量进行左值引用:C ++ 03中的标准容器不移动 - 意识到并且复制矢量可能很昂贵。因此:

void foo(std::vector<int>& v)
{
    // Fill in v...
}

但是,即使在这种情况下,您也应该考虑这种惩罚对您的用例是否真的重要。如果不是,您可能会选择更清晰的功能签名,但代价是CPU周期。

此外,C ++ 03编译器能够执行命名返回值优化,因此即使理论上临时应该从您返回的值进行复制构造,实际上不可能复制发生。

答案 1 :(得分:11)

你自己写的:

  

...这就是为什么STL算法通常会避免分配内存,而不是希望它传入

除了STL算法 通常“希望传入内存”之外,它们对迭代器进行操作。这特别是为了将算法与容器分离,从而产生:

选项8

通过返回输入迭代器,将值生成从这些值的使用和存储中解耦。

最简单的方法是使用boost::function_input_iterator,但下面是草图机制(主要是因为我输入的速度比思考速度快)。


输入迭代器类型

(使用C ++ 11,但你可以用函数指针替换std::function或只是硬编码生成逻辑):

#include <functional>
#include <iterator>
template <typename T>
class Generator: public std::iterator<std::input_iterator_tag, T> {
    int count_;
    std::function<T()> generate_;
public:
    Generator() : count_(0) {}
    Generator(int count, std::function<T()> func) : count_(count)
                                                  , generate_(func) {}
    Generator(Generator const &other) : count_(other.count_)
                                      , generate_(other.generate_) {}
    // move, assignment etc. etc. omitted for brevity
    T operator*() { return generate_(); }
    Generator<T>& operator++() {
        --count_;
        return *this;
    }
    Generator<T> operator++(int) {
        Generator<T> tmp(*this);
        ++*this;
        return tmp;
    }
    bool operator==(Generator<T> const &other) const {
        return count_ == other.count_;
    }
    bool operator!=(Generator<T> const &other) const {
        return !(*this == other);
    }
};

示例生成器函数

(同样,用C ++ 98的外联函数替换lambda是微不足道的,但输入的次数较少)

#include <random>
Generator<int> begin_random_integers(int n) {
    static std::minstd_rand prng;
    static std::uniform_int_distribution<int> pdf;
    Generator<int> rv(n,
                      []() { return pdf(prng); }
                     );
    return rv;
}
Generator<int> end_random_integers() {
    return Generator<int>();
}

使用示例

#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
    using namespace std;
    vector<int> out;

    cout << "copy 5 random ints into a vector\n";
    copy(begin_random_integers(5), end_random_integers(),
         back_inserter(out));
    copy(out.begin(), out.end(),
         ostream_iterator<int>(cout, ", "));

    cout << "\n" "print 2 random ints straight from generator\n";
    copy(begin_random_integers(2), end_random_integers(),
         ostream_iterator<int>(cout, ", "));

    cout << "\n" "reuse vector storage for 3 new ints\n";
    out.clear();
    copy(begin_random_integers(3), end_random_integers(),
         back_inserter(out));
    copy(out.begin(), out.end(),
         ostream_iterator<int>(cout, ", "));
}

答案 2 :(得分:4)

返回vector<int>,它不会被复制,它会被移动。

答案 3 :(得分:4)

在C ++ 11中,正确的答案是返回std::vector<int>以返回它,确保它将被显式或隐式移动。 (首选隐式移动,因为显式移动可以阻止某些优化)

有趣的是,如果你担心重用缓冲区,最简单的方法就是抛出一个可选的参数,该参数的值为std::vector<int>,如下所示:

std::vector<int> get_stuff( int how_many, std::vector<int> retval = std::vector<int>() ) {
  // blah blah
  return retval;
}

并且,如果您有一个正确大小的预分配缓冲区,只需std::move进入get_stuff函数即可使用它。如果您没有正确大小的预分配缓冲区,请不要传递std::vector

实例:http://ideone.com/quqnMQ

我不确定这是否会阻止NRVO / RVO,但是它没有根本原因,移动std::vector足够便宜你可能不会在意它是否会阻止NRVO / RVO无论如何。

但是,您可能实际上不想返回std::vector<int> - 可能您只想迭代相关元素。

在这种情况下,有一种简单的方法和困难的方式。

简单的方法是公开for_each_element( Lambda )方法:

#include <iostream>
struct Foo {
  int get_element(int i) const { return i*2+1; }
  template<typename Lambda>
  void for_each_element( int up_to, Lambda&& f ) {
    for (int i = 0; i < up_to; ++i ) {
      f( get_element(i) );
    }
  }
};
int main() {
  Foo foo;
  foo.for_each_element( 7, [&](int e){
    std::cout << e << "\n";
  });
}

如果您必须隐藏std::function的实施,可能会使用for_each

困难的方法是返回生成相关元素的生成器或一对迭代器。

当你只想一次处理一个元素时,这两个都避免了缓冲区的无意义分配,并且如果生成有问题的值是昂贵的(可能需要遍历内存

在C ++ 98中,我会使用vector&clear()