【问题标题】:C++ Generics and Polymorphism: is this pattern workable?C++ 泛型和多态:这种模式可行吗?
【发布时间】:2018-09-22 19:22:10
【问题描述】:

我了解多态性和泛型如何在其他编程语言(Java、C#、Typescript 等)中进行交互。然而,在 C++ 中,这感觉就像我想使用的模式失败了。

在这个例子中,我想要一个扩展Words 的Names 列表。我想将我的名字列表传递给一个接受单词列表的方法,但我不能。我可以用我的名字填充一个单词列表,但这会丢失类型信息,这意味着我不能调用任何继承到 Name 类的方法。

#include <iostream>
#include <string>
#include <list>

class Word{
    public:
        virtual void say() = 0;
};

class Name : public Word{
    std::string name;
    public:
        Name(std::string name){
            this-> name = name;
        }
        void say() override{
            std::cout << name << std::endl;
        }
        void important_name_function(){
           // Something very important I want to call
        }
};

void say_one(Word* w){
    w-> say();
}

void say_all(std::list<Word*> list){
    for(Word* w: list){
        w-> say();
    }    
}

int main(){
    std::list<Word*> words = {new Name("Kai"), new Name("Ben"), new Name("Sam")};
    say_one(words.front()); //Works, due to the magic of polymorphism
    say_all(words); //Works, due to the magic of polymorphism

    std::list<Name*> names = {new Name("Kai"), new Name("Ben"), new Name("Sam")};
    say_one(names.front()); //STILL works due to the magic of polymorphism AND type information is retained
    say_all(names); //Fails but feels like it shouldn't
}

例如,在 Java 中,我可以通过将 say all 定义为

来解决这个问题
static <T extends Word> void say_all (java.util.LinkedList<T> list){
    for(T w:list){
        w.say();
    }  
}

但是,在 C++ 中寻找这个解决方案在我看来是一个丑陋的解决方案 (C++ equivalent of using <T extends Class> for a java parameter/return type)

对我来说,这意味着以下其中一项是正确的:

  • 这种模式本质上是不可取的,不应该追求。
  • 我认为丑陋的解决方案实际上是最好的解决方案和/或
  • 我错误地访问它,因为它很丑 还有另一种解决方案 创建此模式

【问题讨论】:

  • 查看CRTP 并确定是否可以解决您的问题。
  • 嗨!这解决了我的直接问题。然而,在我的实现 (pastebin.com/9m5c0xtH) 中,我无法弄清楚如何创建words 的列表。我只能创建Names 列表,然后将其传递给请求“扩展单词的事物”列表的方法。这是我需要的 85%,我很感激,但如果我错过了获得完美解决方案的方法,我会很好奇
  • 为什么不只是模板say_all() 函数,像这样? say_all(std::list&lt;T*&gt; list)
  • 你想要类型的协变,C++ 不支持它,因为std::list&lt;X&gt;std::list&lt;Y&gt; 没有任何关系。据我们所知,Y 可能有专门化,使列表表现为队列。没有,但编译器不知道。 std::vector&lt;bool&gt; 有一个不幸的例子。协方差可以用转换运算符实现,但std::list 没有。
  • 另外我建议阅读堆栈、堆和智能指针。应该很少有你应该手写new的例子。如果你写/看到一个,你应该总是很怀疑。

标签: c++ generics polymorphism


【解决方案1】:
  • 我错误地认为它很丑

那个。

我不觉得以下丑陋:

template<class T>
void say_all(const std::list<T*>& list) {
    for (T* w : list) {
        w->say();
    }    
}

请注意,在您的示例中,您根本不必限制 T。在 Java 中无法真正匹配。

仅当您确实需要T 限制为Word 的实例时:

template<class T, typename = std::enable_if_t<std::is_base_of<Word, T>::value>>
void say_all(const std::list<T*>& list) {
    for (T* w : list) {
        w->say();
    }    
}

或者用概念:

template<typename T>
concept IsWord = std::is_base_of<Word, T>::value;

template<class T> requires IsWord<T>
void say_all(const std::list<T*>& list) {
    for(T* w : list) {
        w->say();
    }    
}

旁注:

  • 避免通过引用传递对象来不必要地复制对象。
  • 为减少内存泄漏,请避免使用运算符 new 并改用 std::list&lt;std::unique_ptr&lt;Word&gt;&gt;std::make_unique

【讨论】:

    【解决方案2】:

    您应该能够使用::std::is_base_of type trait 实现类似于 java 的通用功能:

    template
    <
        typename x_Word
    ,   typename x_Enabled = ::std::enable_if_t
        <
            ::std::is_base_of_v<Word, x_Word>
        >
    >
    auto
    say_all(::std::list<x_Word *> & words) -> void
    {
        for(auto & p_w: words)
        {
            p_w->say();
        }    
        return;
    }
    

    online compiler

    【讨论】:

      【解决方案3】:

      你没有看错——这是 C++ 不擅长的。它目前没有等效于 Java 的有界类型参数,这意味着如果您想要对 say_all 可以采取的特定级别的控制,而不是仅仅执行 template&lt;typename T&gt; void say_all(list&lt;T&gt; const&amp; l)(甚至是 template&lt;typename T&gt; void say_all(T const&amp; l))并依靠内部使用抛出错误,你需要手动执行,enable_if 和朋友。

      这是即将推出的 C++“概念”功能旨在解决的问题:

      template<typename T> requires DerivedFrom<T, Word> void say_all(list<T> const& l) { ...
      

      (请注意,语法和标准库支持仍可能发生变化)。

      不过,在这种情况下,如果您尝试传递其他内容的列表,这只是为保证、早期且易于解决的编译器错误服务。老实说,我在这里的方法可能只是记录一下say_all 需要一个子类化 Name 的列表,如果违反,则依赖于可能的编译器错误。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-21
        • 2011-04-20
        • 1970-01-01
        • 2014-10-16
        • 1970-01-01
        • 2012-02-16
        • 1970-01-01
        • 2014-04-20
        相关资源
        最近更新 更多