是否有相当于Java <! - ?在C ++中扩展ClassName - >?

时间:2017-12-26 13:31:25

标签: java c++ templates inheritance

我正在查看此https://docs.oracle.com/javase/tutorial/java/generics/subtyping.htmlhttps://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>

(假设IntegerNumber的子类,如示例链接中所指出的那样。)

使用:

template <typename U>
void farmTest(const AnimalFarm<U *> &farm);

可能是解决方案,但有没有更好的方法在C ++中做到这一点而不会失去Cat或Dog从Animal继承的事实(因为IntegerNumber的子类型)?

谢谢。

3 个答案:

答案 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 ++中的默认不变性不会导致强大的限制。