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

Допустим, у нас есть следующие 2 класса

class Base {
public:
virtual ~Base();
virtual void op(string& s1) = 0;
};

class Derived1 : public Base {
public:
virtual void op(string& s1) override;
};

Все идет нормально. Теперь приходит новое требование, которое требует
создание класса Derived2, однако этому классу нужно 2 строки
как аргументы в оп виртуальной функции. Конечно, если я добавлю функцию оп
с двумя строками в качестве входных данных в Derived2, он перегружен, не переопределяется.
Итак, я должен сделать что-то вроде —

class Base {
public:
virtual ~Base();
virtual void op(string& s1) {}
virtual void op(string& s1, string& s2) {}
};

class Derived1 : public Base {
public:
virtual void op(string& s1) override;
};

class Derived2 : public Base {
public:
virtual void op(string& s1, string& s2) override;
};

Это работает, но я был вынужден добавить вторую опцию в базовом классе.
Кроме того, функции базового класса теперь не являются чисто виртуальными, это
необходимо в противном случае производные классы должны будут реализовать ненужные
оп функции. Это кажется мне немного странным.

Теперь, если новое требование заставляет нас создавать Derived3, один с
другой набор параметров для функции op, нам нужно добавить еще один
Оп функции в базовом классе. В идеале базовый класс не должен меняться, если
кто-то добавляет новый производный класс.

Я уверен, что есть лучший способ сделать это. Довольно уверен, что есть
шаблон для этого, так как это кажется общей проблемой и
должно быть, уже было решено.

Какие-либо предложения ?

Спасибо,
Amarnath

0

Решение

Если у вас есть несколько производных классов, они не «ведут себя» одинаково (например, имеют разное количество аргументов для разных производных классов), у вас есть два варианта (с некоторыми вариациями):

  1. Реализуйте виртуальные функции в базовом классе для всех вариантов.

  2. Ваш класс не является «чистым», и вам, вероятно, нужно «знать», какой это тип в некоторых случаях. использование dynamic_cast (или ваши собственные механизмы обнаружения типов, как в проекте LLVM, который имеет llvm::dyn_cast)

Учти это:

vector<Base*> v;

... insert derived types in v ...

for(auto i : v)
{
v->op( ??? )
}

Как код узнает, если op это 1, 2 или 3 аргумента? Это не может, верно? Таким образом, ваш выбор — опросить класс относительно того, сколько аргументов op берет, всегда берут один и тот же номер (например, op("abc", "", ""); будет использоваться для Derived1, op("abc", "def", ""); за Derived2, а также op("abc", "def", "ghi"); за Derived3).

Или вы могли бы иметь что-то вроде:

class Base {
public:
virtual ~Base();
virtual void op(string& s1) = 0;
virtual void op(string& s1, string &s2) = 0;
... for as many string arguments you need ...
virtual int num_strings() { return 0; }
};

class Derived1 : public Base {
public:
virtual void op(string& s1) override;
virtual int num_strings() override { return 1; }
};

class Derived2 : public Base {
public:
virtual void op(string& s1, string& s2) override;
virtual int num_strings() override { return 2; }
};

Теперь мы можем реализовать наш общий (-ish) цикл:

for(auto i : v)
{
switch(v->num_strings())
{
case 1:
v->op(s1);
break;
case 2:
v->op(s1, s2);
break;
default:
... some handling of unknown number ....
}
}

Ключевым моментом здесь является то, что если вы хотите «сделать» одно и то же для всех объектов, полученных из одного и того же базового класса, они ДОЛЖНЫ иметь одинаковые доступные интерфейсы.

В проекте компилятора я использую llvm::dyn_castпри необходимости, поскольку базовый класс имеет ряд общих операций, но не доступны ВСЕ операции во ВСЕХ производных классах, потому что это сделало бы базовый класс ОГРОМНЫМ числом функций-членов, которые совершенно бесполезны — if не должен иметь те же операции, что и for цикл, и присваивание не требует ничего из того, что либо if или for петля нуждается.

Редактировать: готовя себя, чтобы продолжить день, я подумал о другом решении, которое МОЖЕТ быть правильным в некоторых случаях: используйте op(vector<string>& sv) вместо 1, 2, 3 или n аргументов.

Как и во многих случаях, правильное решение не всегда ясно без дальнейшего понимания точной проблемы.

0

Другие решения

Если взять реплику из ответа Матса и использовать конструктор списка инициализатора c ++, правильный подход может быть (обратите внимание на фигурные скобки {} при вызове op functon):

#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Base {
public:
virtual ~Base() { };
virtual void op(const std::vector<string>& s1) {std::cout<<"Base..."<<s1.at(0)<<std::endl;}
// virtual void op(const stdstring& s1, const string& s2) {std::cout<<"Base..."<<s1<<" "<<s2<<std::endl;}
};

class Derived1 : public Base {
public:
virtual void op(const std::vector<string>& s1) override {std::cout<<"Derived1..."<<s1.at(0)<<std::endl;}
};

class Derived2 : public Base {
public:
virtual void op(const std::vector<string>& s1) {std::cout<<"Derived2..."<<s1.at(0)<<std::endl;}
};
int main()
{
Base *ptr= new Derived2();
ptr->op({std::string("Test1"),std::string("Test2")});

}
0

По вопросам рекламы [email protected]