【问题标题】:Is there an equivalent of the Java <? extends ClassName> in C++?是否有相当于 Java <?在 C++ 中扩展 ClassName>?
【发布时间】:2017-12-26 13:31:24
【问题描述】:

我正在查看 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 的子类型)?

谢谢。

【问题讨论】:

  • 有时这类问题可以通过使用非模板 class AnimalFarmBase {}; 并从该基类 template &lt;typename T&gt; class AnimalFarm : public AnimalFarmBase {}; 继承来解决。
  • AnimalFarmAnimal 没有连接。您似乎想要的是一种定义模板化farmTest 的方法,它只接受AnimalFram&lt;T&gt;,其中TAnimal 类的子类。如果是这样,一种方法是将farmTest 模板化,其中只有Ts 是Animal 的子类。
  • 在 C++ 中,你会用这个解决什么问题?我知道它在 Java 中解决的问题 - 但 IMO,C++ 中不存在这个问题。 很好,您可以使用任何可行的方法来实例化模板,而不管继承如何。唯一的问题是,如果使用了错误的类型,它往往会产生可怕的编译错误消息,但这会(在未来的某一天)通过所谓的概念来解决。
  • 我没有具体的 C++ 问题需要解决。虽然我不是 Java 程序员,但问题来自 Java,然后我只是问自己,我将如何在 C++ 中做到这一点。
  • 这种类型在 Java 中的主要目标是您可以为每个 AnimalFarm 实现一个方法,而不管其类型参数如何,同时仍然确保该类型参数将是Animal 的子类。在 C++ 中,这不是必需的:模板(在这个级别)是一些字符串替换引擎,并且“如果它工作,它工作 - 否则,它不工作”。 (这有很多缺点和限制,超出了本评论的范围)。正如 Christian 已经提到的:boost.org/doc/libs/1_60_0/libs/concept_check/concept_check.htm 的标准版本可能最终会有所帮助......

标签: java c++ templates inheritance


【解决方案1】:

如果您的最终游戏是在 AnimalFarm&lt;Dog *&gt;AnimalFarm&lt;Animal *&gt; 之间形成 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&lt;Animal*&gt; 成为AnimalFarm&lt;Dog*&gt; 的公共基础,函数参数引用将绑定到它。尽管您应该注意到相应的层次结构是平的,无论Animal 有多深。

你可以去看看live

【讨论】:

  • 谢谢,我明白了。但是,您将如何用std::enable_if 而不是std::enabled_if_t 重写这个条件表达式(即使用C++11 而不是C++14),我不熟悉type traits
  • @Scab - 您可以将 std::enabled_if_t&lt;&gt; 替换为 typename std::enabled_if&lt;&gt;::type
  • @StoryTeller 所有这些对于支持is-a 关系真的有必要吗?在我看来,您可以只编写一个模板类并在模板内放置对基类的引用(或指针)(例如,将 Animal* 而不是 T* )。这甚至适用于 C++11 之前的版本。
  • @dsp_user - 引用是不够的。对象的生命周期必须紧密耦合。即便如此,您也必须采取措施阻止AnimalFarm&lt;Animal*&gt; 将自己作为任何类型的基础。并采取措施让引用绑定而不显式调用 getBaseFarm() 之类的东西。
  • 感谢您的快速回复。我有一种情况就是这样做的,这在我的情况下是有效的(我的基类是抽象的(就像在 OP 的示例中一样),所以我只是简单地引用了 Base 以防止实例化不是从 Base 派生的类型。好的,让我们把它留在那里:)
【解决方案2】:

等价可能是我们如何定义等价的问题(如果不是意见)。

如果您想为任何种类的动物提供运行时开放容器...

听起来像是解决运行时多态性的好机会。

运行时多态解决方案在功能上似乎与您所要求的不等效(至少在我看来)。我所知道的关于运行时多态性的最佳描述是Sean Parent's "Better Code: Runtime Polymorphism"talk。

虽然这个想法基本上会隐藏Animal 继承层次结构,所以从容器的角度来看,您可以使用任何容器,如using AnimalFarm = std::vector&lt;Animal&gt;。然后细节进入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();
    }
};

现在要拥有像Dog 这样的Animal,您只需要像这样的免费代码:

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 的初始化构造函数的定义方式(模板化和非explicit)隐式转换为Animal .

我只是手动输入了这段代码。所以很容易出现一些错误。希望它在代码方面显示了这个答案的要点,我希望它对您的需求有所帮助。

如果,OTOH,您希望容器仅包含容器用户定义的特定种类的动物...

然后只将这种动物的实例添加到上述样式容器中。

或者最后,如果你想要编译器强制...

然后执行以下操作:

template <typename T>
using AnimalFarm = std::vector<T>;

void someCodeBlock() {
    AnimalFarm<Dog> dogFarm;
    dogFarm.push_back(Dog{});
}

【讨论】:

    【解决方案3】:

    设计术语

    我第二个 StoryTellers 的回答和建议来看看类型之间的关系:C++ 中的默认关系是 invariance(即没有关系),例如一般用于函数参数、STL 容器和模板。一个例外是智能指针和函数返回类型,它们都是协变的。

    文学

    如果您在 Google 上搜索“方差”、“协方差”和“逆变”等术语,您会发现几篇有用的文章。

    Scott Meyers 的 Effective C++ 和 More Effective 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)variance,其中唯一提供两者的类型是 std::function&lt;R *(T *)&gt;,具有协变返回类型和逆变参数。此外,它还展示了处理这些关系的各种方法。

    最佳实践

    在规范、设计和形式验证中,准标准是不变的通用性和返回类型和参数,如std::function&lt;R *(T *)&gt;。但是,还有其他意见,例如Betrand Meyers 的书 Object-Oriented Software Construction 和他的语言 Eiffel 推广协变函数参数。

    对于 C++,我相信不变泛型是最合适的:因为完全模板特化,协变没有意义:你可以完全特化 AnimalFarm&lt;Cat&gt;,所以它肯定不满足与 AnimalFarm&lt;Animal&gt; 的 is-a 关系。由于静态鸭子类型,C++ 中的默认不变性不会造成强烈的限制。

    【讨论】:

      猜你喜欢
      • 2016-01-07
      • 2011-06-11
      • 1970-01-01
      • 2011-03-08
      • 1970-01-01
      • 2021-10-09
      • 2014-11-27
      • 2019-04-24
      • 1970-01-01
      相关资源
      最近更新 更多