我有一个函数,它接受一个数字并返回那么多东西(比方说,整数)。什么是最干净的界面?一些想法:
vector<int>
。矢量将被复制多次,效率很低。vector<int>*
。我的getter现在必须分配向量本身以及元素。有谁必须释放向量的所有常见问题,你不能分配一次并使用相同的存储器来进行对getter的许多不同调用等。这就是为什么STL算法通常避免分配内存,而是想要它传了进来。unique_ptr<vector<int>>
。现在很清楚是谁删除了它,但我们还有其他问题。vector<int>
作为参考参数。 getter可以push_back()
,调用者可以决定是否reserve()
空间。但是,如果传入的vector
非空,那么getter应该怎么做?附加?通过先清除它来覆盖?断言它是空的?如果函数的签名只允许一次解释,那就太好了。begin
和end
迭代器。现在我们需要返回实际写入的项目数(可能小于预期),并且调用者需要注意不要访问从未写入的项目。iterator
,来电者可以通过insert_iterator
。char *
。 :)答案 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算法 通常“希望传入内存”之外,它们对迭代器进行操作。这特别是为了将算法与容器分离,从而产生:
通过返回输入迭代器,将值生成从这些值的使用和存储中解耦。
最简单的方法是使用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
。
我不确定这是否会阻止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()
。