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来排除这些值。我可以在返回模板类型的类中编写函数吗?喜欢:
class A
{
<class T> GetVar()
}
此外,我觉得我正在以完全不正确的方式处理此问题。我可以这样混合模板和继承吗?如果没有,应该如何设计?
答案 0 :(得分:3)
您有几种选择。我将首先解释我的首选解决方案。
如果您有一个基类类型的数组,为什么还要用Var
做东西?该变量特定于子类。如果某个地方有A
,那么您甚至都不必关心B
在那个地方有什么。
类型变量的操作应封装在基类的虚函数中。如果您想做条件和事情,也许您可以将该条件封装到一个返回布尔值的虚拟函数中。
有时候,您预先知道将进入该列表的类型数量。使用变体并删除基类是一个很好的解决方案,可能适用于您的情况。
假设您只有int
,double
和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]
);
完全丢弃基类,并对在其上执行操作的函数进行模板化。您可以将所有功能移至一个接口或多个std::function
中。代替该功能直接操作。
这是我的意思的示例:
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
,它可能会迅速导致代码膨胀。
您可以创建一个分配给正确类型的访问者。
此解决方案与您所需要的解决方案非常接近,但是相当麻烦,并且在添加案例时很容易被破坏。
类似这样的东西:
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 : Visitor, T {
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
。
std::any
这实际上是返回带有擦除类型的变量。您不能对其进行任何操作,除非将其回退:
class A {
std::any GetVar()
};
我个人不喜欢这种解决方案,因为它很容易损坏并且根本不是通用的。在那种情况下,我什至不使用多态。
答案 1 :(得分:1)
我认为这将是最简单的方法。只需将比较方法移至接口并在派生类中覆盖它即可。在您的示例中添加以下行:
class A
{
/*..................................................*/
virtual bool comp(const int) const { return false; }
virtual bool comp(const std::string) const { return false; }
virtual bool comp(const double) const { return false; }
};
template<class T>
class B : public A
{
/*..................................................*/
virtual bool comp(const T othr) const override { return othr == Var; }
};
void foo()
{
/*..................................................*/
if (i->comp(20))
{
/* do something*/
}
if (i->comp("Hello Stackoverflow"))
{
/* do something*/
}
/*..................................................*/
}