C ++泛型和多态性:这种模式可行吗?

时间:2018-09-22 19:22:10

标签: c++ generics polymorphism

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

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

Word

例如,在Java中,我可以通过将所有人都定义为

来解决此问题
#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
}

但是,在C ++中寻找这种解决方案会让我眼前一亮,就像一个丑陋的解决方案(C++ equivalent of using <T extends Class> for a java parameter/return type

对我来说,这意味着下列条件之一是正确的:

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

3 个答案:

答案 0 :(得分:0)

  
      
  • 我错误地认为它很丑
  •   

那个。

我找不到以下丑陋的地方:

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<std::unique_ptr<Word>>std::make_unique

答案 1 :(得分:0)

您应该能够使用::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

答案 2 :(得分:0)

您没看错-C ++并不擅长此事。它目前没有等效于Java的有界类型参数,这意味着,如果您希望对say_all可以采取什么措施进行特定级别的控制,而不是仅仅进行template<typename T> void say_all(list<T> const& l)(甚至是{{1} }),并依靠内部用法抛出错误,则需要与template<typename T> void say_all(T const& l)和朋友一起手动进行。

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

enable_if

(请注意,语法和标准库支持仍可能更改)。

不过,在这种情况下,如果您尝试传递其他列表,那么这只是在保证有保证,尽早且易于解决的编译器错误。诚实的说,我在这里的方法可能只是记录template<typename T> requires DerivedFrom<T, Word> void say_all(list<T> const& l) { ... 期望包含某个子类say_all的列表,并且如果违反该列表,则依赖于可能的编译器错误。