Переопределить чистую виртуальную функцию с другим параметром производного типа

Есть много вопросов по этой теме, но я не нашел решения для моего следующего вопроса:

У меня есть следующие классы:

1) Чистый виртуальный класс

class Employee {
private:
    vector<Employee> vec_employee;

public:
    Employee() {};
    virtual ~Employee() {};
    virtual void set_vec_subordinate(vector<Employee> vec_employee) = 0;
};

2) производный класс

class Worker : Employee{ private: public: };

3) Другой производный класс, который должен переопределить чистый виртуальный метод из Employee.

class Manager : Employee {
private:
public:
    inline void set_vec_subordinate(vector<Worker> vec_employee) override { this->set_vec_subordinate(vec_employee); };
};

Чего я пытаюсь добиться, так это переопределить чисто виртуальный метод, но использовать «другой» параметр. Так что все еще новичок в C++, но я думаю, что должен быть способ сделать это, особенно потому, что другой параметр относится к типу Worker, который является производным классом от Employee.


person Moritz Kampfinger    schedule 07.05.2020    source источник
comment
Даже если С++ допускает контравариантность и применяет ее к вектору, ваш метод не является контравариантным. Вы не будете поддерживать vector<Manager> (в отличие от Employee).   -  person Jarod42    schedule 07.05.2020
comment
C++ поддерживает только ковариацию возвращаемого типа из указателя/ссылки. (и мы не можем добавить отношение ковариации (например, для умного указателя)).   -  person Jarod42    schedule 07.05.2020
comment
Терминология: класс, имеющий одну или несколько чистых виртуальных функций, является абстрактным классом. Нет такой вещи, как чистый виртуальный класс.   -  person Pete Becker    schedule 07.05.2020


Ответы (1)


Невозможно сделать в точности то, что вы планируете (и для этого есть веская причина).

Ваш код также по своей сути сломан, поскольку вы используете тип vector<Employee>, для которого требуются объекты типа Employee, которые не могут существовать, поскольку Employee является абстрактным классом. Вместо этого вы можете использовать vector ссылочного типа, например, vector<shared_ptr<Employee>>. (В остальной части этого ответа этот факт замалчивается, чтобы сделать его более читабельным.)

Также обратите внимание, что void Manager::set_vec_subordinate(vector<Worker> vec_employee) override { this->set_vec_subordinate(vec_employee); }; вызовет бесконечный цикл (вероятно, приводящий к переполнению стека) при вызове, так как он будет продолжать вызывать сам себя.

У класса Employee есть контракт со своими пользователями, в котором говорится, что следующий код должен быть допустимым (при условии заданных функций get_boss и get_workers):

Employee& boss = get_boss();
vector<Employee> subordinate_vec = get_workers();
boss.set_vec_subordinate(subordinate_vec);

Это может не иметь семантического смысла для вашего приложения, но синтаксис языка программирования означает, что это должно быть возможно. Некоторые языки программирования (не C++!) допускают подобные ковариантные вызовы:

Employee& boss = get_boss();
vector<Worker> subordinate_vec = get_workers();
boss.set_vec_subordinate(subordinate_vec);  // Invalid C++: `vector<Worker` cannot be converted to `vector<Employee>` implicitly

Хотя действительно возможно создать контейнер в C++, который ведет себя таким образом, что такое использование возможно, с этим проще справиться, сделав функцию set_vec_subordinate шаблоном, который требует произвольного контейнера объектов, которые неявно преобразуемы или производное от Employee, а затем просто преобразование объектов во время операции копирования (поскольку вектор в этом случае все равно не перемещается).

Вторая идея заключается в том, что должна быть возможность изменить сигнатуру функции при ее переопределении. Это возможно в C++ путем реализации базового варианта (который должен быть бинарно-совместимым с, т. е. равным, сигнатурой версии Employee, поскольку она будет вызываться и в этом случае), а затем добавлением дополнительных перегрузок. Например, вы можете сделать что-то вроде:

class Manager : Employee {
private:
public:
    inline void set_vec_subordinate(vector<Employee> vec_employee) override { this->vec_employee = std::move(vec_employee); };
    inline void set_vec_subordinate(vector<Worker> const& vec_worker) {
        vec_employee = std::vector<Employee>(vec_worker.begin(), vec_worker.end());  // copy convert all elements
    };
};
person gha.st    schedule 07.05.2020
comment
Спасибо за этот действительно хороший ответ. Это помогает мне лучше понять C++ и, надеюсь, улучшит мои навыки программирования. - person Moritz Kampfinger; 08.05.2020