Допустим, у нас есть следующие 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
Если у вас есть несколько производных классов, они не «ведут себя» одинаково (например, имеют разное количество аргументов для разных производных классов), у вас есть два варианта (с некоторыми вариациями):
Реализуйте виртуальные функции в базовом классе для всех вариантов.
Ваш класс не является «чистым», и вам, вероятно, нужно «знать», какой это тип в некоторых случаях. использование 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 аргументов.
Как и во многих случаях, правильное решение не всегда ясно без дальнейшего понимания точной проблемы.
Если взять реплику из ответа Матса и использовать конструктор списка инициализатора 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")});
}