迭代不同的 CRTP Derived class 方法

Iterate over different CRTP Derived class methods

在下面的示例中,我有一个非常典型的 CRTP 示例,两个不同的派生 classes 都有一个方法 bar。基础 class 有一个方法 foo 只是转发给一些派生的 bar 方法

#include <iostream>

template<typename Derived>
class Base {
public:
    void foo() {
        static_cast<Derived*>(this)->bar();
    }
};

class DerivedA : public Base<DerivedA> {
public:
    void bar() {
        ::std::cout << "A\n";
    }
};

class DerivedB : public Base<DerivedB> {
public:
    void bar() {
        ::std::cout << "B\n";
    }
};

int main() {
    DerivedA a;
    DerivedB b;
    a.foo();
    b.foo();
}

我似乎无法拥有基数 class 的数组/向量等,因为它必须具有符合 Base<T> 的类型,其中 T 不同

是否有某种没有 virtual 的约定能够迭代不同的派生 classes 假设它们都具有相同的方法(bar 在这种情况下)?

It doesn't seem like I can have an array / vector / etc. of the base class because it would have to have a type along the lines of Base<T> where T is different.

对于所有 T,你可以有一个 Base<T> 的基 class,然后,你可以有一个 list/vector/array 指向基 class 的指针,如果这对你有用。

struct BaseOne
{
   virtual void foo() = 0;
   virtual ~BaseOne() {}
};

template<typename Derived>
class Base : struct BaseOne {
public:
    void foo() {
        static_cast<Derived*>(this)->bar();
    }
};

然后,

int main() {
    std::vector<BaseOne*> v {new DerivedA, new DerivedB };
    for ( auto item : v )
       item->bar();

    for ( auto item : v )
       delete item;
}

Is there some kind of convention without virtual for being able to iterate over different derived classes assuming they all have the same method (bar in this case)?

没有,没有。

您可以使用 Boost.Variant。例如:

typedef boost::variant<DerivedA, DerivedB> Derived;

struct BarCaller : public boost::static_visitor<void> {
    template <class T>
    void operator()(T& obj) {
        obj.bar();
    }
};

int main() {
    std::vector<Derived> vec{DerivedA(), DerivedB(), DerivedA()};

    BarCaller bar;
    for (Derived& obj : vec) {
        obj.apply_visitor(bar);
    }
}

这使您可以将异构类型存储在向量或其他 STL 容器中(通过使用 "discriminated union"),并允许您对所有这些类型调用特定函数,而不管它们是否没有共同的祖先或任何虚拟方法。

截至目前,变体已成为C++17标准的一部分,问题的解决方案可以通过std::variantstd::visit解决,如下所示。

示例中的模板class是Interface<>并使用CRTP惯用语强制派生class实现helloImpl():

#include <iostream>
#include <vector>
#include <variant>

template<typename Implementer>
struct Interface {
    void hello() const {
        static_cast<Implementer const *>(this)->helloImpl();
    }
};

几个 class 示例,具有 helloImpl()

的不同实现
struct Hello1 : public Interface<Hello1> {
    void helloImpl() const {
        std::cout << "Hello1" << std::endl;
    }
};

struct Hello2 : public Interface<Hello2> {
    void helloImpl() const {
        std::cout << "Hello2" << std::endl;
    }
};

下面是如何使用它在 vector<> 容器中存储数据及其遍历:

int main() {
    using var_t = std::variant<Hello1, Hello2>;
    std::vector<var_t> items{Hello1(), Hello1(), Hello2()};

    for(auto &item: items) {
        std::visit([](auto &&arg) {
            arg.hello();
        }, item);
    }
    return 0;
}