C ++ Generics and Polymorphism: этот шаблон работает? - PullRequest
0 голосов
/ 22 сентября 2018

Я понимаю, как полиморфизм и обобщенные элементы взаимодействуют в других языках программирования (Java, C #, Typescript и т. Д.).Однако в C ++ это похоже на шаблон, который я хотел бы использовать с ошибками.

В этом примере я хочу получить список Name с, который расширяет Word с.Я хочу передать свой список имен в метод, который принимает список слов, но я не могу.Я могу заполнить список слов с моими именами, но при этом теряется информация о типе, что означает, что я не могу вызывать методы, наследуемые для класса 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 Я мог бы решить эту проблему, определив, скажем, все как

static <T extends Word> void say_all (java.util.LinkedList<T> list){
    for(T w:list){
        w.say();
    }  
}

Однако, поиск этого решения в C ++ дает то, что на мой взгляд выглядит как уродливое решение ( C ++ эквивалент использованиядля параметра java / тип возвращаемого значения )

Для меня это означает, что одно из следующего является верным:

  • Этот шаблон по своей природе нежелателен и не должен преследоваться.
  • Решение, которое я отклонил как некрасивое, на самом деле является лучшим решением и / или
  • Я неправильно обращаюсь к нему как к некрасивому. Существует другое решение для создания этого шаблона

Ответы [ 3 ]

0 голосов
/ 23 сентября 2018

Вы должны иметь возможность реализовать универсальную функцию, аналогичную java, используя ::std::is_base_of trait type :

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;
}

онлайн-компилятор

0 голосов
/ 23 сентября 2018

Вы не ошибаетесь - это то, в чем С ++ не очень хорош.В настоящее время он не имеет эквивалента параметров ограниченного типа Java, что означает, что если вы хотите, чтобы этот конкретный уровень контроля над тем, что может взять say_all, вместо того, чтобы просто делать template<typename T> void say_all(list<T> const& l) (или даже template<typename T> void say_all(T const& l)) и рассчитывать навнутреннее использование для выдачи ошибок, вам нужно будет сделать это вручную, с enable_if и друзьями.

Это то, что, возможно, предназначается будущей функции "концепций" C ++:

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

(Обратите внимание, что синтаксис и поддержка стандартной библиотеки все еще могут быть изменены).

Тем не менее, в этом случае это просто обслуживание гарантированной, ранней и простой в устранении ошибки компилятора, есливы пытаетесь передать список чего-то еще. Честно говоря, мой подход здесь, вероятно, заключается в том, чтобы просто документально подтвердить, что say_all ожидает список чего-то подкласса Name, и полагаться на возможную ошибку компилятора, если это будет нарушено.

0 голосов
/ 23 сентября 2018
  • Я неправильно оцениваю его как некрасивый

То.

Я не нахожу следующего ужасного:

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 вместо.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...