将模板与多态性混合

2022-01-24 00:00:00 templates polymorphism c++
class A
{
    friend void foo();
    virtual void print_Var() const{};

};// does not contain variable Var;


template<class T>
class B : public A
{
    T Var;
public:
    B(T x):Var(x){}
    void print_Var() const override
    {
        std::cout<<Var<<std::endl;
    }
};

void foo()
{
    std::array<std::unique_ptr<A>, 3> Arr = {
            std::make_unique<B<int>>(100),
            std::make_unique<B<int>>(20),
            std::make_unique<B<std::string>>("Hello Stackoverflow")
    };
            std::shuffle(Arr.begin(), Arr.end(), std::mt19937(std::random_device()())); // 3rd parameter generated by Clang-Tidy

    for (auto &i: Arr)
    {
        i->print_Var(); // OK
      //  auto z = i->Var   // no member named Var in A
                            // obviously base class does not contain such variable

     //   if (i->Var==20) {/* do something*/}
     //   if (i->Var=="Hello Stackoverflow") {/* do something*/}

    }
}

说明:我想遍历指向 A 的指针数组,其中填充了指向从 A 派生的类的指针,并且根据变量 Var 的类型,执行一些 if() 语句.问题是我无法访问 Var,因为它不是基类的成员.但是,可以通过例如返回 void 的重载函数来计算这些值.我可以在返回模板类型的类中编写函数吗?喜欢:

Explanation: I want to iterate over array of pointers to A, which is filled with pointers to classes derived from A, and depending on what type is variable Var, do some if( ) statement. Problem is that i cannot access Var, cause its not member of base class. However, it's possible to cout those values by, for example, overloaded function returning void. Could i write function in A class that returns templated type? like:

class A
{
    <class T> GetVar()
}

此外,我觉得我正在以完全不正确的方式处理这个问题.我可以像这样混合模板和继承吗?如果不是,应该如何设计?

Besides, I feel like I'm dealing with this problem in totally improper way. Can i mix templates and inheritance like that? If not, how should it be designed?

推荐答案

你有几个选择.我将首先解释我的首选解决方案.

You have a few choices. I'll explain my preferred solution first.

如果你有一个基类类型的数组,你为什么还要用 Var 做一些事情?该变量特定于子类.如果你在某处有一个 A,你甚至不应该关心 B 在那个地方有什么或没有什么.

If you have an array of a base class type, why do you even want to do stuff with Var? That variable is specific to the child class. If you have a A somewhere, you shouldn't even care what B has or hasn't at that place.

对类型变量的操作应该封装在基类的虚函数中.如果你想做条件和东西,也许你可以将该条件封装到一个返回布尔值的虚函数中.

Operations on the typed variable should be encapsulated in virtual function in the base class. If you want to do condition and stuff, maybe you could encapsulate that condition into a virtual function that returns a boolean.

有时,您会提前知道将进入该列表的类型数量.使用变体并删除基类是一个很好的解决方案,可能适用于您的情况.

Sometimes, you know in advance the amount of types that will go into that list. Using a variant and drop the base class is a good solution that may apply to your case.

假设你只有 intdoublestd::string:

Let's say you only have int, double and std::string:

using poly = std::variant<B<int>, B<double>, B<std::string>>;

std::array<poly, 3> arr;

arr[0] = B<int>{};
arr[1] = B<double>{};
arr[2] = B<std::string>{};
// arr[2] = B<widget>{}; // error, not in the variant type

std::visit(
    [](auto& b) {
        using T = std::decay_t<decltype(b)>;
        if constexpr (std::is_same_v<B<int>, T>) {
            b.Var = 2; // yay!
        }
    },
    arr[0]
);

2b.删除基类并使用泛型函数

完全放弃基类,并将对它们进行操作的函数模板化.您可以将所有函数移动到一个接口或多个 std::function 中.对其进行操作,而不是直接对函数进行操作.

2b. Drop the base class and use generic functions

Drop the base class entirely, and template your functions that do operation on them. You can move all your function into an interface or many std::function. Operate on that instead of the function directly.

这是我的意思的一个例子:

Here's an example of what I meant:

template<typename T>
void useA(T const& a) {
    a.Var = 34; // Yay, direct access!
}

struct B {
    std::function<void()> useA;
};

void createBWithInt() {
    A<int> a;
    B b;

    b.useA = [a]{
        useA(a);
    };
};

这适用于您只有少量操作的情况.但是如果你有很多操作或者如果你有很多类型的std::function,它很快就会导致代码膨胀.

This is fine for cases where you only have few operations. But it can quickly lead to code bloat if you have a lot of operations or if you have many types of std::function.

您可以创建一个分派到正确类型的访问者.

You could create a visitor that dispatch to the right type.

此解决方案将与您的解决方案非常接近,但非常繁琐,并且在添加案例时很容易中断.

This solution would be much close to what you except, but is quite combersome and can break easily when adding cases.

类似这样的:

struct B_Details {
protected:
    struct Visitor {
        virtual accept(int) = 0;
        virtual void accept(double) = 0;
        virtual void accept(std::string) = 0;
        virtual void accept(some_type) = 0;
    };

    template<typename T>
    struct VisitorImpl : T, Visitor {
        void accept(int value) override {
            T::operator()(value);
        }

        void accept(double) override {
            T::operator()(value);
        }

        void accept(std::string) override {
            T::operator()(value);
        }

        void accept(some_type) override {
            T::operator()(value);
        }
    };
};

template<typename T>
struct B : private B_Details {
    template<typename F>
    void visit(F f) {
        dispatch_visitor(VisitorImpl<F>{f});
    }

private:
    virtual void dispatch_visitor(Visitor const&) = 0;
};

// later

B* b = ...;

b->visit([](auto const& Var) {
    // Var is the right type here
});

那么当然,你必须为每个子类实现dispatch_visitor.

Then of course, you have to implement the dispatch_visitor for each child class.

这是乱写返回带有擦除类型的变量.你不能对它做任何操作而不把它扔回去:

This is litteraly returning the variable with type erasure. You cannot do any operation on it without casting it back:

class A {
    std::any GetVar()
};

我个人不喜欢这种解决方案,因为它很容易损坏并且根本不通用.在那种情况下,我什至不会使用多态性.

I personnaly don't like this solution because it can break easily and is not generic at all. I would not even use polymorphism in that case.

相关文章