27

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

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

4 回答 4

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 编译器能够执行命名返回值优化,因此即使理论上应该从您返回的值复制构建临时文件,但实际上不会发生复制。

于 2013-05-09T17:21:17.100 回答
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, ", "));
}
于 2013-05-09T18:55:06.060 回答
4

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

于 2013-05-09T17:21:07.230 回答
4

在 C++11 中,正确的答案是 returnstd::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,但没有根本原因会阻止它,而且移动 astd::vector足够便宜,您可能不会关心它是否会阻止 NRVO/RVO。

但是,您可能实际上并不想返回 a 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 中,我会采用 avector&clear()它。

于 2013-05-09T17:58:03.530 回答