#c #oop #c 14 #c 17
#c #ооп #c 14 #c 17
Вопрос:
Я пишу небольшую базу данных и, похоже, столкнулся с проблемой. У меня есть 3 Statement
типа (каждый из которых соответствует CREATE
, INSERT
и SELECT
. Определения этих типов:
class Statement { }; // Base class, contains common stuff like the table name
class CreateStatement : public Statement {
/* Contains information regarding the CREATE statement after parsing (name and datatype of
each column) */
}
class InsertStatement : public Statement {
/* Contains information regarding the INSERT statement (list of values to enter inside the DB) */
}
class SelectStatement : public Statement {
/* Contains information regarding the SELECT statement ( list of attributes to select from the DB) */
}
Мой анализатор имеет функции для надлежащего анализа этих 3 типов операторов ввода, но я возвращаю базовый класс из каждой из этих функций. Примером является:
Statement parse_create(const std::stringamp; stmt) {
CreateStatement response;
// Parse stmt
return response;
}
Мое обоснование этого заключалось в том, чтобы избежать случаев if / else в моем основном цикле REPL (вместо проверки, имеет ли ввод тип create, а затем условный возврат CreateStatement
или какой-либо другой производный класс, просто вызовите parse
один раз, и это вернет Statement
объект). Проблема, с которой я сталкиваюсь, заключается в выполнении после этого возврата Statement
. У меня есть 3 разные функции для выполнения этих команд ( execute_create
, execute_insert
и execute_select
), и они принимают производные классы в качестве параметров соответственно ( CreateStatement
, InsertStatement
и SelectStatement
). Это делается для того, чтобы функции выполнения могли использовать информацию, которая была сохранена в объектах производного класса во время синтаксического анализа.
Могу ли я использовать какую-то шаблонную логику для вызова соответствующей функции execute или я потерял информацию из производного класса, как только вернул базовый класс после синтаксического анализа? Я пытался делать такие вещи, как:
Statement parsed_stmt = parse(input); // input is the string the user entered
if (std::is_same_v<decltype(input), CreateStatement>) {
execute_create(parsed_stmt);
}
но это, очевидно, не сработает из-за явного указания типа Statement
.
Я также был бы признателен за любые отзывы о дизайне. Спасибо!
Комментарии:
1. Когда вы возвращаете значение по значению, вы разрезаете объекты. Полиморфизм работает только со ссылками или указателями. Это, конечно, предполагает, что ваша иерархия классов изначально полиморфна. Если это не так, то указатели или ссылки на базовый класс не будут работать без большого количества приведений вверх и вниз.
2. Инструмент перехода к такого рода вещам — это виртуальная функция, а не шаблон. Вы уже пробовали это?
3. Вы не можете возвращать по значению базовый тип, это приведет к нарезке вашего объекта и удалению всех производных функций. Добро пожаловать в C , где функции борются друг с другом из-за плохого дизайна.
Ответ №1:
Способ, которым я бы это сделал, был бы примерно таким:
class Statement {
...
virtual void parse(const std::string amp;stmt) = 0; // A string view will work here too.
virtual bool execute() = 0;
...
};
class Create : public Statement {
...
virtual void parse(const std::string amp;stmt); // Implement
virtual bool execute(); // Implement
...
};
... // more statements
а затем соответствующим образом реализовать их в производных классах. Вы могли бы проанализировать невиртуальный и вызвать виртуальный create
метод, но мне это кажется излишним. Создание примечания ничего не возвращает — оно просто изменяет состояние объекта (сохраняет оператор или что-то еще).
Аналогично, execute
хранится в производном классе, поэтому он знает, как выполнить себя — даже если у вас есть ссылка на Statement
объект, правильный производный метод будет вызван без дополнительных усилий. Я предположил, что возвращается логическое значение, указывающее на успех.
Это означает, что вам нужно работать со ссылками (или указателями) на операторы. Это основная сила наследования с полиморфизмом — возможность одинаково работать с различными производными объектами, не беспокоясь о том, что они собой представляют.
Комментарии:
1. Большое спасибо, очень признателен. Это решило мою проблему. Приступайте к выполнению новых проектов и изучению языка!
Ответ №2:
Динамическая отправка:
struct Base {
abstract virtual void method() = 0;
virtual ~Base() = default;
};
struct Derived: Base {
void method() override {}
};
Статическая отправка (CRTP), обычно излишняя:
template <class D> struct Base {
void method() {
static_cast<D*>(this)->method();
}
};
struct Derived: Base<Derived> {
void method() { ... }
};