【发布时间】:2020-02-08 11:31:46
【问题描述】:
我正在处理需要某个容器类来保存自定义类的变体(尤其是在向量中收集此类实例)的情况。这些又是相互关联的。在代码示例中,此变体中的类型为Bird 和Fish,容器类为AnimalContainer(完整的工作代码见下文)。
课程概览不完整:
using namespace std;
using uint = unsigned int;
class Animal {
protected:
uint length_;
};
class Fish : public Animal {
private:
uint depths_of_dive_;
};
class Bird : public Animal {
private:
uint wing_span_;
};
class AnimalContainer {
private:
variant<Bird, Fish> the_animal_;
};
现在(忽略企鹅和其他一些鸟类),鸟类通常不会潜水,鱼没有翅膀(至少没听说过)。但是,代码应该提供通过AnimalContainer 类的实例a 使用a.WingSpan() 请求wing_span_ 的可能性,如果这个动物是Bird,以及使用@987654331 的depth_of_dive_ @,应该是Fish。此外,对于每个Bird 和Fish,可以估计一个(生理上不现实的)权重,即可以调用a.EstimatedWeight()。
基本上为了避免编译器错误,在Fish类中添加了一个方法WingSpan(),在Bird类中添加了DepthOfDive()。
添加这些虚拟方法可能会变得非常麻烦,尤其是当涉及两个以上的变体(此处为Fish 和Bird)时,或者当这些类包含许多方法时。
一种可能性似乎使特定类的访问者超载并在所有其他情况下返回一些警告(再次使用通用 lambda),但即使这稍微改进了过程,它也相当麻烦(参见第二个代码下面的例子)。
您对如何以更全面、需要更少复制和粘贴的方式处理此问题有什么建议吗?如果您对此概念有一般性问题,也欢迎提出建议。
顺便说一下,动物容器类后来被放置在另一个引导用户的类中,以避免意外调用虚拟函数。
第一个工作代码示例
#include <variant>
#include <iostream>
using namespace std;
using uint = unsigned int;
class Animal {
public:
Animal(uint length) : length_{length} {}
uint Length() { return length_; }
protected:
uint length_;
};
class Fish : public Animal {
public:
Fish(uint length, uint depths_of_dive) : Animal(length), depths_of_dive_{depths_of_dive} {}
uint DepthOfDive() { return depths_of_dive_; }
uint EstimatedWeight() { return length_ * length_; }
uint WingSpan() { cerr << "Usually fishes do not have wings... "; return 0; }
private:
uint depths_of_dive_;
};
class Bird : public Animal {
public:
Bird(uint length, uint wing_span) : Animal(length), wing_span_{wing_span} {}
uint WingSpan() { return wing_span_; }
uint EstimatedWeight() { return wing_span_ * length_; }
uint DepthOfDive() { cerr << "Usually birds can not dive... "; return 0; }
private:
uint wing_span_;
};
class AnimalContainer {
public:
AnimalContainer(Bird b) : the_animal_{b} {}
AnimalContainer(Fish f) : the_animal_{f} {}
uint Length() {
return visit([] (auto arg) { return arg.Length(); }, the_animal_);
}
uint WingSpan() {
return visit([] (auto arg) { return arg.WingSpan(); }, the_animal_);
}
uint DepthOfDive() {
return visit([] (auto arg) { return arg.DepthOfDive(); }, the_animal_);
}
uint EstimatedWeight() {
return visit([] (auto arg) { return arg.EstimatedWeight(); }, the_animal_);
}
private:
variant<Bird, Fish> the_animal_;
};
int main()
{
Fish f(2,3);
Bird b(2,3);
AnimalContainer a_1(f);
AnimalContainer a_2(b);
cout << a_1.Length() << ' ' << a_1.WingSpan() << ' ' << a_1.DepthOfDive() << ' ' << a_1.EstimatedWeight() << endl;
cout << a_2.Length() << ' ' << a_2.WingSpan() << ' ' << a_2.DepthOfDive() << ' ' << a_2.EstimatedWeight() << endl;
return 0;
}
第二个工作代码示例
#include <variant>
#include <iostream>
using namespace std;
using uint = unsigned int;
class Animal {
public:
Animal(uint length) : length_{length} {}
uint Length() { return length_; }
protected:
uint length_;
};
class Fish : public Animal {
public:
Fish(uint length, uint depths_of_dive) : Animal(length), depths_of_dive_{depths_of_dive} {}
uint DepthOfDive() { return depths_of_dive_; }
uint EstimatedWeight() { return length_ * length_; }
// no more dummy function
private:
uint depths_of_dive_;
};
class Bird : public Animal {
public:
Bird(uint length, uint wing_span) : Animal(length), wing_span_{wing_span} {}
uint WingSpan() { return wing_span_; }
uint EstimatedWeight() { return wing_span_ * length_; }
// no more dummy function
private:
uint wing_span_;
};
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
class AnimalContainer {
public:
AnimalContainer(Bird b) : the_animal_{b} {}
AnimalContainer(Fish f) : the_animal_{f} {}
uint Length() {
return visit([] (auto arg) { return arg.Length(); }, the_animal_);
}
uint WingSpan() {
return visit(overloaded { // now overloaded version
[] (auto) { cerr << "This animal does not have wings... "; return uint(0); },
[] (Bird arg) { return arg.WingSpan(); }}, the_animal_);
}
uint DepthOfDive() {
return visit(overloaded { // now overloaded version
[] (auto) { cerr << "This animal can not dive... "; return uint(0); },
[] (Fish arg) { return arg.DepthOfDive(); }}, the_animal_);
}
uint EstimatedWeight() {
return visit([] (auto arg) { return arg.EstimatedWeight(); }, the_animal_);
}
private:
variant<Bird, Fish> the_animal_;
};
int main()
{
Fish f(2,3);
Bird b(2,3);
AnimalContainer a_1(f);
AnimalContainer a_2(b);
cout << a_1.Length() << ' ' << a_1.WingSpan() << ' ' << a_1.DepthOfDive() << ' ' << a_1.EstimatedWeight() << endl;
cout << a_2.Length() << ' ' << a_2.WingSpan() << ' ' << a_2.DepthOfDive() << ' ' << a_2.EstimatedWeight() << endl;
return 0;
}
【问题讨论】:
-
请注意,变体通常是继承的替代。如果您有一个类层次结构,您通常通过(智能)指针访问基类的对象。变体对于完全不相关的类型很有用。
标签: c++ variant visitor-pattern