如何将 std::generate/generate_n 与多态函数对象一起使用?

发布于 2024-10-17 17:10:35 字数 1482 浏览 3 评论 0原文

我是 std::generate 的新手,并尝试构建一个使用它来初始化向量的程序。然而它的表现与我的期望不同。

我有一个抽象基类:

template <typename G>
class RandomAllele {
 public:
  RandomAllele() { /* empty */ }
  virtual ~RandomAllele() { /* empty */ }

  virtual G operator()() const = 0;
}; 

它的扩展方式(例如):

class RandomInt : public RandomAllele<int> {
 public:
  RandomInt(int max) : max_(max) {}
  int operator()() const { return rand() % max_; }
 private:
  int max_;
};

我通过指针将继承类的实例传递给工厂类,然后将其用作 std::generate 的第三个参数:

template<typename G, typename F>
class IndividualFactory {
 public:
  IndividualFactory(int length, const RandomAllele<G> *random_allele)
      : length_(length), random_allele_(random_allele) { /* empty */ }

  individual_type *generate_random() const {
    std::vector<G> *chromosome = new std::vector<G>(length_);
    std::generate(chromosome->begin(), chromosome->end(), *random_allele_); */

    return new individual_type(chromosome);
  }
 private:
  int length_;
  RandomAllele<G> *random_allele_;
};

现在我收到错误说 RandomAllele 无法实例化,因为它是一个抽象类。当指针已经存在时,为什么generate需要实例化它?为什么它尝试使用基类而不是继承类RandomInt?

如果我将 std::generate 替换为:,则效果很好,

for(auto iter = chromosome->begin(); iter != chromosome->end(); ++iter)
  *iter = (*random_allele_)();

但我仍然希望了解为什么它的行为很奇怪,并且如果有办法做到这一点,我更愿意使用generate。

感谢您抽出时间,

里斯

I'm new to std::generate and have attempted to structure a program which uses it to initialize vectors. However it's behaving differently to my expectations.

I have an abstract base class:

template <typename G>
class RandomAllele {
 public:
  RandomAllele() { /* empty */ }
  virtual ~RandomAllele() { /* empty */ }

  virtual G operator()() const = 0;
}; 

Which is extended by (for example):

class RandomInt : public RandomAllele<int> {
 public:
  RandomInt(int max) : max_(max) {}
  int operator()() const { return rand() % max_; }
 private:
  int max_;
};

I pass an instance of my inheriting class to a factory class by pointer, and then use it as the third argument for std::generate:

template<typename G, typename F>
class IndividualFactory {
 public:
  IndividualFactory(int length, const RandomAllele<G> *random_allele)
      : length_(length), random_allele_(random_allele) { /* empty */ }

  individual_type *generate_random() const {
    std::vector<G> *chromosome = new std::vector<G>(length_);
    std::generate(chromosome->begin(), chromosome->end(), *random_allele_); */

    return new individual_type(chromosome);
  }
 private:
  int length_;
  RandomAllele<G> *random_allele_;
};

Now I get an error saying that RandomAllele cannot be instantiated because it's an abstract class. Why does generate need to instantiate it when the pointer already exists? And why is it trying to use the base class instead of the inheriting class RandomInt?

This works fine if I replace std::generate with:

for(auto iter = chromosome->begin(); iter != chromosome->end(); ++iter)
  *iter = (*random_allele_)();

But I still wish to understand why it behaves strangely, and I'd prefer to use generate if there is a way to do this.

Thanks for your time,

Rhys

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

天涯沦落人 2024-10-24 17:10:35

正如其他人上面提到的,generategenerate_n 函数按值获取生成器对象,从而阻止您在此上下文中直接使用继承。

然而,您可以做的一个技巧是应用软件工程基本定理:

任何问题都可以通过添加另一层间接来解决

,而不是直接传入多态仿函数,而是传入一个包装仿函数,该包装仿函数存储指向该多态仿函数的指针,然后适当地转发调用。例如:

template <typename T> class IndirectFunctor {
public:
    explicit IndirectFunctor(RandomAllele<T>* f) : functor(f) {
         // Handled in initializer list
    }

    T operator() () const {
        return (*functor)();
    }

private:
    RandomAllele<T>* functor;
};

如果您随后将此对象传递给generate,如下所示:

RandomAllele<T>* functor = /* ... create an allele ... */
std::generate(begin, end, IndirectFunctor<T>(functor));

那么一切都会按预期工作。这样做的原因是,如果您按值复制 IndirectFunctor,那么您只需浅复制存储的指针,该指针仍将指向您想要的 RandomAllele称呼。这避免了您遇到的切片问题,因为它从不尝试通过基类指针直接复制 RandomAllele 类型的对象。它总是复制包装器对象,而包装器对象从不尝试复制 RandomAllele

希望这有帮助!

As others have mentioned above, the generate and generate_n functions take their generator objects by value, precluding you from directly using inheritance in this context.

However, one trick you can do is to apply the Fundamental Theorem of Software Engineering:

Any problem can be solved by adding another layer of indirection

Rather than directly passing in a polymorphic functor, instead pass in a wrapper functor that stores a pointer to this polymorphic functor and then forwards the call appropriately. For example:

template <typename T> class IndirectFunctor {
public:
    explicit IndirectFunctor(RandomAllele<T>* f) : functor(f) {
         // Handled in initializer list
    }

    T operator() () const {
        return (*functor)();
    }

private:
    RandomAllele<T>* functor;
};

If you then pass this object into generate, as seen here:

RandomAllele<T>* functor = /* ... create an allele ... */
std::generate(begin, end, IndirectFunctor<T>(functor));

Then everything will work as intended. The reason for this is that if you copy IndirectFunctor<T> by value, then you just shallow-copy the stored pointer, which will still point to the RandomAllele you want to call. This avoids the slicing problem you were encountering because it never tries directly copying an object of type RandomAllele through a base class pointer. It always copies the wrapper object, which never tries to duplicate RandomAllele.

Hope this helps!

这样的小城市 2024-10-24 17:10:35

std::generate 的生成器是按值传递的,因此是复制的。

std::generate's generator is passed by value, and therefore copied.

雪化雨蝶 2024-10-24 17:10:35

一般来说,C++ 标准库实现静态多态性(模板),并且不支持函数对象的运行时多态性(虚拟方法)。这是因为它按值传递所有函数对象,假设它们是无状态或几乎无状态,这样通过指针或引用传递的附加间接方式将比按值传递更昂贵。

由于它是按值传递的,因此会导致切片,并且当您尝试使用 RandomAllele 时,它会认为您指的是那个确切的类,而不是它实际指向的任何派生类型。无需在 G 上进行模板化,只需直接在您想要的确切生成器函子类型上进行模板化即可。

In general the C++ standard library implements static polymorphism (templates) and doesn't support runtime polymorphism (virtual methods) for function objects. This is because it passes all its function objects by values, assuming them to be stateless or almost stateless such that the added indirection of passing by pointer or reference would be more expensive than by value.

Since it's passed by value this results in slicing and when you try to use a RandomAllele<G> it thinks you mean that exact class not whatever derived type it actually points to. Instead of templating on G just template on the exact generator functor type you desired directly.

星星的軌跡 2024-10-24 17:10:35

问题是所有标准算法都按值获取参数,以符合传统的 C 约束。因此,这里的 std::generate() 算法按值获取函子。您的函子类型为 RandomAllele,属于抽象类型。是的,它是一个指向具体类型的指针,但该指针是一个抽象类型。在复制该对象时,算法调用 RandomAllele 的复制构造函数;即,算法构造抽象类型的实例。这是 C++ 语言所禁止的。

您可以告诉运行时环境不要太担心,如下所示:

RandomInt *cp = dynamic_cast<RandomInt*>(random_allele);
if( ! cp ) {
    // i thought the pointer is of RandomInt. It isn't. Err.
    std::terminate(); // or something
}
std::generate(chromosome->begin(), chromosome->end(), *cp);

The issue is that all standard algorithms take their arguments by value, to conform with traditional C constraints. So here the std::generate() algorithm take the functor by value. Your functor, of type RandomAllele<int>, is of abstract type. Yes, it's a pointer pointing at a concrete type, but the pointer is of an abstract type. In copying this object, the algorithm calls the copy constructor of RandomAllele<int>; i.e., the algorithm constructs an instance of abstract type. And this is something the C++ language forbids.

You can tell the runtime environment not to worry too much like so:

RandomInt *cp = dynamic_cast<RandomInt*>(random_allele);
if( ! cp ) {
    // i thought the pointer is of RandomInt. It isn't. Err.
    std::terminate(); // or something
}
std::generate(chromosome->begin(), chromosome->end(), *cp);
[浮城] 2024-10-24 17:10:35

原型是:

template <class ForwardIterator, class Generator>
void generate ( ForwardIterator first, ForwardIterator last, Generator gen );

因此 gen 是按值传递的,因此编译器尝试通过复制构造 RandomAllele,因此出现问题。

解决方案是使用信封来提供所需的间接:

template<class G>
class RandomAlleleEnvelope
{
public:
    RandomAlleleEnvelope(const RandomAllele<G>* ra)
        : ra_(ra)
    {}
      int operator()() const { return (*ra_)(); }
private:

    const RandomAllele<G>* ra_;
};

  std::generate<std::vector<int>::iterator,RandomAlleleEnvelope<int> >(chromosome->begin(), chromosome->end(), random_allele_); 

另请注意,还有另一种解决方案,定义您自己的生成以使用引用:

template <class ForwardIterator, class Generator>
  void referenceGenerate ( ForwardIterator first, ForwardIterator last, 
                           const Generator& gen )
{
  while (first != last)  *first++ = gen();
}
 referenceGenerate(chromosome->begin(), chromosome->end(), *random_allele_); 

我还认为以下应该工作,即使用标准生成并显式使其处理引用类型:

std::generate<std::vector<int>::iterator, const RandomAllele<int>& >
                   (chromosome->begin(), chromosome->end(), *random_allele_); 

我说应该是因为在 VS2010 上实例化失败。另一方面,如果我可以定义自己的:

  template <class ForwardIterator, class Generator>
  void myGenerate ( ForwardIterator first, ForwardIterator last, Generator gen )
  {
     while (first != last)  *first++ = gen();
  }
  myGenerate<std::vector<int>::iterator, const RandomAllele<int>& >
        (chromosome->begin(), chromosome->end(), *random_allele_); 

VS2010 会失败,因为它实现了 std::generate 是另一个 std::generate 的术语,它默认返回非引用参数。

The prototype is:

template <class ForwardIterator, class Generator>
void generate ( ForwardIterator first, ForwardIterator last, Generator gen );

Hence gen is passed by value, so the compiler attempts to construct a RandomAllele by copy, hence problem.

The solution is to use an Envelope to provide the needed indirection:

template<class G>
class RandomAlleleEnvelope
{
public:
    RandomAlleleEnvelope(const RandomAllele<G>* ra)
        : ra_(ra)
    {}
      int operator()() const { return (*ra_)(); }
private:

    const RandomAllele<G>* ra_;
};

  std::generate<std::vector<int>::iterator,RandomAlleleEnvelope<int> >(chromosome->begin(), chromosome->end(), random_allele_); 

Also note there is another solution, define your own generate to use a reference:

template <class ForwardIterator, class Generator>
  void referenceGenerate ( ForwardIterator first, ForwardIterator last, 
                           const Generator& gen )
{
  while (first != last)  *first++ = gen();
}
 referenceGenerate(chromosome->begin(), chromosome->end(), *random_allele_); 

I also think the following should work, that is to use the standard generate and explicitly make it handle a reference type:

std::generate<std::vector<int>::iterator, const RandomAllele<int>& >
                   (chromosome->begin(), chromosome->end(), *random_allele_); 

I say should because this fails is instantiate on VS2010. On the other hand, if I can define my own:

  template <class ForwardIterator, class Generator>
  void myGenerate ( ForwardIterator first, ForwardIterator last, Generator gen )
  {
     while (first != last)  *first++ = gen();
  }
  myGenerate<std::vector<int>::iterator, const RandomAllele<int>& >
        (chromosome->begin(), chromosome->end(), *random_allele_); 

The VS2010 fails because it implements std::generate is terms of another std::generate which defaults back to non-reference parameters.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文