Наследование не работает должным образом

Краткая версия: это проект по управлению освещением. Некоторыми из классов являются Pin и Channel. Канал содержит пин. Pin является базовым классом для DigitalOutPin и будет базовым классом для AnalogOutPin, и я хочу, чтобы Channel мог использовать любой дочерний класс Pin.

Pin имеет абстрактный метод setPinValue(), но когда я создаю объект DigitalOutPin и вызываю setPinValue(), вызывается Pin.setPinValue(), а не DigitalOutPin.setPinValue(), и моя программа не работает, потому что Pin.setPinValue () пуст, так как он должен быть перезаписан.

Длинная версия: Если я пропущу определение Pin.setPinValue(), я получу самое идиотское из сообщений об ошибках gcc: «неопределенная ссылка на `vtable for Pin'». Если я сделаю это чисто виртуальным, я получу несколько ошибок в виде

In file included from /devel/arduino/lightbright/LightBrightMain/LightBrightMain.ino:9:0:
/devel/arduino/libraries/LightBrightLib/Channel.h:21:20: error: cannot declare parameter 'pin' to be of abstract type 'Pin'
     void begin(Pin pin, int channelNumber);
                ^
In file included from /devel/arduino/libraries/LightBrightLib/DigitalOutPin.h:10:0,
             from /devel/arduino/lightbright/LightBrightMain/LightBrightMain.ino:8:
/devel/arduino/libraries/LightBrightLib/Pin.h:16:7: note:   because the following virtual functions are pure within 'Pin':
 class Pin
   ^
/devel/arduino/libraries/LightBrightLib/Pin.h:33:18: note:  virtual void Pin::setPinValue(int)
     virtual void setPinValue(int value)=0;
              ^
In file included from /devel/arduino/lightbright/LightBrightMain/LightBrightMain.ino:9:0:
/devel/arduino/libraries/LightBrightLib/Channel.h:23:9: error: invalid abstract return type for member function 'Pin Channel::getPin()'
 Pin getPin();
     ^
In file included from /devel/arduino/libraries/LightBrightLib/DigitalOutPin.h:10:0,
             from /devel/arduino/lightbright/LightBrightMain/LightBrightMain.ino:8:
/devel/arduino/libraries/LightBrightLib/Pin.h:16:7: note:   since type 'Pin' has pure virtual functions
 class Pin
   ^

Полный код находится на GitHub, но вот соответствующие части: Вывод объявлен как чистый виртуальный прямо сейчас, но я пробовал с =0 и без него

class Pin
{
  public:
   ...
  protected:
   ...
  // Этот метод специфичен для ребенка. В этом базовом классе пусто
  virtual void setPinValue(int value)=0;
};

DigitalOutPin объявляется как

class DigitalOutPin : public Pin
{
  public:
    void on();
    void off();
  protected:
    void setPinValue(int value);
};

и определяется как

void DigitalOutPin::setPinValue(int value) {
....
}

Отказ от ответственности: я в первую очередь Java-разработчик, поэтому C++ OO для меня очень отличается, но, насколько я могу судить, это должно работать так, как задумано.

Спасибо за любой совет, который вы можете дать.

, 👍1

Обсуждение

Ваш DigitalOutPin «защищен» — он должен быть «общедоступным», чтобы соответствовать определению базового класса и разрешать доступ вне класса., @Majenko

Особенно во встроенном контексте вы можете воспользоваться преимуществами _final_, которые позволяют компилятору создавать более эффективный двоичный код: http://en.cppreference.com/w/cpp/language/final, @Igor Stoppa

С той же точки зрения, если вы можете избежать VMT и поиска во время выполнения, это определенно более эффективно. Один из способов сделать это — превратить отношения is_a в отношения has_a., @Igor Stoppa

@Majenko Похоже, это может быть так! Первоначально у меня был другой общедоступный метод, который вызывал этот, и, вероятно, тогда он сломался. Я попробую это сегодня вечером., @dj_segfault

@IgorStoppa Очень хорошее предложение. Спасибо. Проверю эту ссылку сегодня вечером., @dj_segfault

Наследование @IgorStoppa WRT против композиции, я специально хочу, чтобы канал содержал любого дочернего элемента Pin, так что это не сработает здесь, но полезно знать в других обстоятельствах. Еще раз спасибо., @dj_segfault

@Majenko На самом деле, перечитывая код, я пропустил строку **protected:** при сокращении кода выше. Я просто исправил это в вопросе. Он защищен в Pin и DigitalOutPin. Извините, что ввел вас в заблуждение, но проблема не в этом., @dj_segfault


1 ответ


Лучший ответ:

0

Я взял ваш код из приведенного выше и скомпилировал его для ПК в Visual Studio. Когда я создаю объект типа DigitalOutPin и вызываю setPinValue(), он вызывает ожидаемую функцию. Таким образом, это означает, что либо вам удалось найти ошибку в компиляторе gcc, либо в вашем коде есть что-то еще, что вступает в игру. Можете ли вы попробовать написать ОЧЕНЬ простую минимальную тестовую программу, которая определяет класс Pin и класс DigitalOutPin (только с функциями setPinValue, которые печатают строку), а также функции настройки и цикла. Просто чтобы узнать, нашли ли вы ошибку gcc.

Даже если это решит проблему наследования, у вас возникнут другие проблемы с вашим кодом, потому что C++ странный. В Channel.h ваши переменные Pin должны быть указателями, это позволит им вызывать функции в производных классах, а не в функциях базового класса.

Я думаю, вы можете улучшить производительность, используя "const int& foo" для ваших параметров. Константа означает, что функция не может ее изменить, это позволяет компилятору обрабатывать ее по-разному, а символ "&" означает использовать ссылку на переменную, а не создавать ее новую копию.

Функции, которые не изменяют переменные-члены в коде, также должны быть объявлены постоянными. "int FooBar(const int& value) const;" Это делает его более понятным для читателя, и компилятор, вероятно, делает некоторые странные вещи для повышения производительности. (Если вы пишете однострочные функции, возможно, вам захочется исследовать "встроенные" функции.)

Наконец, я бы рекомендовал использовать конструкторы и деструкторы. В релизных сборках переменным не присваивается значение. Если эти переменные являются членами класса, это означает, что вы не знаете, с чего начать. Также производные классы должны инициализировать базовый класс, поэтому:

// Конструктор класса для DigitalOutPin
DigitalOutPin::DigitalOutPin(const int& pinNumber)
  : Pin(pinNumber)  // Инициализировать базовый класс
  , m_State(false)  // Инициализировать все члены класса (в порядке, указанном в файле .h)
{
  // Много красивого кода
}
// Деструктор класса для DigitalOutPin (объявлен как виртуальный)
DigitalOutPin::~DigitalOutPin()
{}
,

Спасибо, Мэтт. Проведя дополнительные исследования, я начал думать, что проблема в том, что он хотел где-то создать экземпляр Pin, и я пришел к тому же выводу, что этому каналу нужен *Pin, а не Pin, и изменил все его использование для разыменования указателя. В тот момент это сработало, как и ожидалось. Мне все еще нужно сделать большинство ссылок постоянными параметрами, но теперь это работает. Спасибо ., @dj_segfault

Что касается конструкторов и деструкторов, насколько я понимаю, конструкторы имеют очень ограниченное значение в мире Arduino, и лучше всего иметь метод begin() для инициализации объекта, из-за порядка инициализации, что может произойти в цикле ( ), что может произойти в setup(),..., @dj_segfault