C ++ Как приводить динамически производные классы - PullRequest
0 голосов
/ 19 января 2019

Я пишу переводчик на с ++ в качестве курсовой работы в универе. По сути, я сам перевожу этот интерпретатор Python на c ++ с помощью Google.

с использованием посетителя для переводчика У меня есть 2 класса

BinOp : public AST

Number : public AST

В моем классе переводчиков есть 2 метода

class Interpreter : public NodeVisitor

int visitBinOp(BinOp* node)
{
  //example if the operation is +
  //return this->visit(node->left) + this->visit(node->right)
}
int visitNumber(Number* node)
{
  //returns the int value that's in the node.
  //return node->value;
}

и 1 метод в NodeVisitor, который интерпретатор наследует

class NodeVisitor
int visit(AST* node)
{

  //if node is BinOp properFunction is pointer to visitBinOp

  //if node is Number properFunction is pointer tp visitNumber

  //return properFunction(node)
}

Вопрос 1 : Какой лучший способ проверить, является ли AST BinOp или номером

if(typeid(node) == typeid(BinOp*)

или через некоторое приведение (при попытке dynamic_cast я получаю ошибку, что классы не полиморфны).

Основная проблема : Мне нужно как-то создать указатель на эти функции, но я не знаю, как это сделать.

РЕДАКТИРОВАТЬ 1 Добавил этот код в NodeVisitor, но из-за него, включая «Interpreter.h» и переводчик, включая "NodeVisitor.h", я получаю

ошибка C2504: «NodeVisitor»: базовый класс не определен.

unsigned long int NodeVisitor::visit(AST* node)
{
  std::function<unsigned long int(Number* node)> visitNumber = std::bind(&Interpreter::VisitNumber);
  std::function<unsigned long int(BinaryOperation* node)> visitBinOp = std::bind(&Interpreter::VisitBinOp);
  if (typeid(node) == typeid(Number*))
  {
    visitNumber((Number*)node);
  }
  if (typeid(node) == typeid(BinaryOperation*))
  {
    visitBinOp((BinaryOperation*)node);
  }
}

Я думаю, что мне нужно добавить extern "C", чтобы посещать функции BinOp и visitNumber и использовать этот подход, упомянутый в здесь

void *handle = dlsym(0, RTLD_LOCAL | RTLD_LAZY);
FunctionType *fptr = (FunctionType *)dlsym(handle, "visitBinOp/visitNumber");
fptr();

Но я не совсем уверен, как это работает.

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Примечание: хотя иногда может потребоваться dynamic_cast<>, имейте в виду, что его использование - это "кодовый запах".

В объектно-ориентированном программировании вы обычно не запрашиваете объект для его типа, а затем делаете что-то на основе этой информации. Вы говорите объекту сделать то, что вам нужно, и он будет действовать правильно в зависимости от того, что это за объект.

Так почему бы просто не дать AST метод virtual unsigned long InterpreterVisit(Interpreter* interpreter) и сделать

unsigned long Interpreter::visit(AST *node) {
    node->InterpreterVisit(this);
}

вместо этого? Если вы хотите сохранить код интерпретатора отдельно от кода AST, вы также можете реализовать его как вложение, где у вас есть второй объект , принадлежащий узлу AST, который реализует InterpreterVisit(), а затем просто нужно одно место, которое создает правильный тип объекта вложения для этого типа (например, с использованием частичной специализации шаблона).

Я не знаю достаточно о структуре вашего переводчика, чтобы сказать, работает ли это для вашего дизайна, но я хотел бы призвать вас сделать паузу и подумать, есть ли лучший способ, чем dynamic_cast<>, когда вы чувствуете искушение используйте это.

Иногда это также может помочь просто «спрятать» использование броска, особенно если вы обнаружите, что часто делаете то же самое. Сделай это один раз, заранее. Прикрепите объект к объектам на основе этого приведения, затем просто вызывайте этот объект вместо того, чтобы приводить его снова и снова.

PS: Что касается вашего циркуляра, иногда вы можете избежать таких ситуаций. Существует несколько инструментов:

  1. Объявите вперед один из участвующих классов, используя class Foo; (обратите внимание на точку с запятой) вместо включения его заголовка. Это скажет C ++, что класс с таким именем существует без извлечения всего заголовка и использования другого класса. Вы не можете объявить подклассы объявленного заранее класса, но вы можете объявить ссылки и указатели на него.

  2. Разделите ваши заголовки. Обычно каждый класс получает свой собственный заголовок (.h / .hpp) и файл реализации (.cp / .cpp). Таким образом, вы можете включить только те классы, которые вам нужны, и заранее объявить эти классы в заголовках других (и затем только фактически включить полный заголовок в файлы реализации, которые используют эти классы).

  3. Разделите свои классы сами. То есть иметь один класс MixinA с классом деталей A, который необходимо включить, другой класс MixinB с классом деталей B должен включить, а затем создать class C : public MixinA, public MixinB .... Таким образом, у вас есть класс, который делает оба, но избегает круга, потому что только C видит всю картину.

0 голосов
/ 19 января 2019

Хорошо, сегодня утром я проснулся с ответом. Вместо реализации функции посещения в NodeVisitor.cpp, где я не могу получить доступ к функциям Интерпретатора, я сделал функцию посещения виртуальной и реализовал ее в Interpreter.cpp

unsigned long int Interpreter::visit(AST* node)
{
  Number* number = dynamic_cast<Number*>(node);
  BinaryOperation* binOp = dynamic_cast<BinaryOperation*>(node);

  if (number)
  {
    return this->VisitNumber((Number*)node);
  }

  return this->VisitBinOp((BinaryOperation*)node);
}

Полагаю, мой мозг просто нуждался в некотором перерыве ... Кодировал на работе 8 часов, а потом дома 4, 12 часов подряд: D

...