我正在查看此https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html和https://docs.oracle.com/javase/tutorial/java/generics/inheritance.html,并问自己如何在C ++中实现它。
我有一个小例子来说明:
#include <iostream>
class Animal
{
public:
virtual std::string type() const = 0;
virtual ~Animal() {}
};
class Dog : public Animal
{
public:
virtual std::string type() const {
return "I am a dog";
}
};
class Cat : public Animal
{
public:
virtual std::string type() const {
return "I am a cat";
}
};
template <typename T>
class AnimalFarm
{
};
void farmTest(const AnimalFarm<Animal *> &farm)
{
std::cout << "test farm";
}
int main(int argc, char *argv[])
{
AnimalFarm<Dog *> dogFarm;
AnimalFarm<Animal *> animalFarm;
farmTest(animalFarm); // OK
farmTest(dogFarm); // NOK compiler error as class AnimalFarm<Dog *> does not inherits from class AnimalFarm<Animal *>
return 0;
}
我理解为什么它在C ++中不起作用。在Java中,解决方案是使用以下构造:
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
(假设Integer
是Number
的子类,如示例链接中所指出的那样。)
使用:
template <typename U>
void farmTest(const AnimalFarm<U *> &farm);
可能是解决方案,但有没有更好的方法在C ++中做到这一点而不会失去Cat或Dog从Animal继承的事实(因为Integer
是Number
的子类型)?
谢谢。
答案 0 :(得分:5)
如果您的最终游戏是在AnimalFarm<Dog *>
和AnimalFarm<Animal *>
之间形成 is-a 关系,那么一些带有类型特征的模板专业化可以做到这一点。
template <typename T, typename = void>
class AnimalFarm // Primary template
{
};
template<typename T>
class AnimalFarm<T*,
std::enable_if_t<
!std::is_same<T, Animal>::value &&
std::is_base_of<Animal, T>::value
>
> // Specialization only instantiated when both conditions hold
// Otherwise SFINAE
: public AnimalFarm<Animal*>
{
};
由于AnimalFarm<Animal*>
成为AnimalFarm<Dog*>
的公共基础,因此函数参数引用将绑定到它。虽然您应该注意到相应的层次结构是平的,无论Animal
有多深。
您可以查看 live 。
答案 1 :(得分:1)
对等可能是我们如何定义等价(如果不是意见)的确切问题。
如果你想为任何种类的动物打开运行时...
听起来像运行时多态解决的好机会。
运行时多态解决方案看起来功能上相似,如果不等同于(至少在我的想法中)与您要求的相同。我所知道的运行时多态性的最佳描述是Sean Parent's "Better Code: Runtime Polymorphism" talk。
基本上,虽然这个想法会隐藏Animal
继承层次结构,但从容器的角度来看,你可以使用using AnimalFarm = std::vector<Animal>
之类的任何容器。然后详细介绍Animal
的实现,如下所示:
class Animal {
struct Concept {
virtual ~Concept() = default;
virtual std::string type() const = 0;
// add in more methods for any other properties your Animal "concept" has
};
template <typename T>
struct Model final: Concept {
Model(T arg): data{std::move(arg)} {}
std::string type() const override {
return get_type_name(data);
}
// override define whatever other methods the concept has
T data; // stores the underlying type's data
};
std::shared_ptr<const Concept> m_self;
public:
template <typename T>
Animal(T v): m_self{std::make_shared<Model<T>>(std::move(v))} {}
// default or delete other constructors as needed.
friend std::string get_type_name(const Animal& animal) {
return animal.m_self->type();
}
};
现在要像Animal
一样拥有Dog
,您只需要免费代码:
struct Dog {
};
inline std::string get_type_name(const Dog& dog) {
return "Dog";
}
使用(假设至少是C ++ 11)就像......
using AnimalFarm = std::vector<Animal>;
int main() {
auto theFarm = AnimalFarm{};
theFarm.push_back(Dog{});
for (const auto& e: theFarm) {
std::cout << get_type_name(e) << "\n";
}
return 0;
}
请注意,此处Dog
已实例化(Dog{}
)然后隐式变为Animal
,因为Animal
初始化构造函数的方式是已定义(模板化和非explicit
)。
我只是手动输入此代码。所以它容易出错。希望它在代码方面显示了这个答案的主旨,但我希望它能满足您的需求。
如果,OTOH,您希望容器只有一个特定种类的动物由用户定义......
然后只将动物种类的实例添加到上面的样式容器中。
或者最后,如果您想要编译器实施......
然后执行以下操作:
template <typename T>
using AnimalFarm = std::vector<T>;
void someCodeBlock() {
AnimalFarm<Dog> dogFarm;
dogFarm.push_back(Dog{});
}
答案 2 :(得分:0)
我的第二个StoryTellers回答和建议来看看类型之间的关系:C ++中的默认关系是不变性(即没有关系),例如一般来说,用于函数参数,STL容器和模板。一个例外是智能指针和函数返回类型,它们都是协变的。
如果你谷歌搜索术语&#34; variance&#34;,&#34; covariance&#34;和#34;逆变&#34;,你会发现一些有用的文章。
Scott Meyers&#39;有效的C ++和更有效的C ++触及了基础。
Sumant Tambe有detailed article in his C++ Truths blog about covariance and contravariance in C++, with a focus on the standard library。它包含一个表,列出了哪些std库类型具有co(ntra)方差,其中唯一提供两者的类型是std::function<R *(T *)>
,其中包含协变返回类型和逆变量参数。此外,它显示了这些关系的各种方法。
在规范,设计和形式验证中,准标准是不变的通用性,并返回std::function<R *(T *)>
中的类型和参数。但是,还有其他意见,例如Betrand Meyers预订Object-Oriented Software Construction和他的语言Eiffel,促进协变函数论证。
对于C ++,我认为不变的通用性是最合适的:由于总模板专业化,协方差没有意义:你可以完全专门化AnimalFarm<Cat>
,因此它肯定不符合与{{的is-a关系1}}。由于静态鸭子类型,C ++中的默认不变性不会导致强大的限制。