Наследование не работает должным образом
Краткая версия: это проект по управлению освещением. Некоторыми из классов являются 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 для меня очень отличается, но, насколько я могу судить, это должно работать так, как задумано.
Спасибо за любой совет, который вы можете дать.
@dj_segfault, 👍1
Обсуждение1 ответ
Лучший ответ:
Я взял ваш код из приведенного выше и скомпилировал его для ПК в 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
- Какие накладные расходы и другие соображения существуют при использовании структуры по сравнению с классом?
- Как перебрать объекты или передать объект функции?
- Прерывания внутри класса, связанные с функцией класса
- Как писать скетчи, совместимые с makefile?
- Как правильно поместить дескриптор u8g2 в класс
- использование ссылок на SFR в встроенном ассемблере gcc
- Ошибка сегментации и огромная потребность в SRAM для Serial.println
- Как объявить массив переменного размера (глобально)
Ваш 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