最近我读到,如果传递的仿函数是有状态的(具有内部副作用),则某些STL算法具有未定义的行为。我已将std::generate
函数与类似(不太重要)的仿函数用于以下内容:
class Gen
{
public:
explicit Gen(int start = 0)
: next(start)
{
}
int operator() ()
{
return next++;
}
private:
int next;
};
与std::generate
一起使用是否安全?生成值的顺序是否有保证?
答案 0 :(得分:5)
几乎每个有意义的发电机都是有状态的。想一想:生成器调用没有参数。如果发电机是无状态的,那么每次都必须产生相同的值。换句话说,唯一的无状态生成器是常量生成器:
template <typename T>
struct constant {
constant(T&& value) : value{value} {}
T const& operator ()() const { return value; }
private:
T const value;
};
或者,或者,您可以拥有一个在对象本身外部具有状态的生成器,例如std::random_device
。但这些只是两个适用的案例。这将是非常严格的限制。所以是的 - 绝对有可能拥有一个有状态的发电机。
您所引用的此声明的来源是不谈论生成器 - 相反,它是在谈论谓词,它们是一组不同的函数,并且不同的要求(谓词是一个带有单个参数的函数,返回bool
值)。
答案 1 :(得分:5)
使用具有 std :: generate 等功能的有状态仿函数没有问题,但必须注意不要遇到底层实现制作副本的问题以与开发人员的想法不同的方式更改程序的语义。
25.1p10
算法库 - 常规[algorithms.general]
[注意:除非另有说明,否则允许将函数对象作为参数的算法自由复制这些函数对象。对象标识非常重要的程序员应考虑使用指向非复制实现对象(如reference_wrapper)或某些等效解决方案的包装类。 - 结束记录]
标准明确指出将对发生器进行精确last - first
(N)分配和调用,但它不会以什么顺序进行状态。可以在以下 Q&amp; A :
std::generate
,不安全?一般不会,但有一些警告。
在 std :: generate 中,标准保证将为给定范围内的每个元素调用仿函数类型的相同实例,但可以通过 std的声明暗示:: generate 我们可能遇到的问题是,我们忘记了传递的仿函数在内部调用了该函数将是副本的一个人作为论点通过了。
请参阅下面的代码段,我们在其中声明仿函数以生成&#34; unique&#34; IDS:
#include <iostream>
#include <algorithm>
template<class id_type>
struct id_generator {
id_type operator() () {
return ++idx;
}
id_type next_id () const {
return idx + 1;
}
id_type idx {};
};
int main () {
id_generator<int> gen;
std::vector<int> vec1 (5);
std::vector<int> vec2 (5);
std::generate (vec1.begin (), vec1.end (), gen);
std::generate (vec2.begin (), vec2.end (), gen);
std::cout << gen.next_id () << std::endl; // will print '1'
}
运行上述内容后,我们可能希望gen.next_id ()
获得11
,因为我们已经使用生成 5个vec1
的ID,以及5个ID vec2
。
情况并非如此,因为在调用 std :: generate 时, id_generator 的实例将被复制,并且它是复制将在函数内部使用。
此问题有多种解决方案,当您将仿函数传递给与 std相关的某些算法函数时,所有这些都会阻止复制: :生成
备选方案#1
推荐的解决方案是使用来自std::reference_wrapper
的std::ref
将仿函数打包到<functional>
。这将有效地复制 reference_wrapper ,但引用的 generate_id 实例将保持不变。
std::generate (vec1.begin (), vec1.end (), std::ref (gen));
std::generate (vec2.begin (), vec2.end (), std::ref (gen));
std::cout << gen.next_id () << std::endl; // will print '11'
备选方案#2
当然,你可以通过写下如下令人困惑的东西来使你的手指更强壮:
std::generate<decltype(vec1.begin()), id_generator<int>&>(vec1.begin(), vec1.end(), gen);
答案 2 :(得分:0)
就标准而言,您无法保证。一个 实现可以随时复制功能对象。 这个想法是一个实现可能会分裂 范围分为子范围,并在一个单独的范围内处理每个子范围 线。 (至少,这是在某一点声称的 标准化过程。)
当然,在全球范围内,对于某些算法,例如generate
或
accumulate
,允许多个副本或重新排序
算法没用。通常,这样的算法保证
订购;他们似乎忘记了generate
。和
而标准确实似乎忘记了允许复制,
实际上,如果订单得到保证,则没有必要
处理中有多个副本,所以我认为我们可以放心
假设没有,即使在没有严格的情况下也是如此
保证。