LINUX.ORG.RU

Как сделать реализацию виртуального метода статическим?

 , ,


0

2

Есть у меня базовый класс AbstractChannel. От него наследуется три класса: FileChannel, TcpChannel, ComChannel. Выглядит он примерно вот так:

class AbstractChannel : public QObject
{
    Q_OBJECT

public:
    AbstractChannel(QObject *parent = 0);
    virtual ~AbstractChannel();

    virtual int getChar(num_t Port);
    virtual bool isFileDef(num_t Port);

    virtual void writeProc(const void *ptrs, size_t size, num_t Port);
    virtual bool isRw(num_t Port);
    virtual TCHAN_Type channelType(num_t Port);
};

Создавался AbstractChannel для того, чтобы можно было передать указатель на производные классы FileChannel/TcpChannel/ComChannel в некий класс парсера.

Проблема в том, что парсер использует все вышеперечисленные методы как методы обратного вызова. Ну, к примеру, если парсеру нужно получить данные из потока, он вызывает метод getChar(). И поэтому все эти методы должны быть статическими.

Однако, если в классах FileChannel/TcpChannel/ComChannel сделать реализацию этих методов со спецификатором static, то будет ошибка:
error: ‘static int FileChannel::getChar(num_t)’ cannot be declared
since ‘virtual int AbstractChannel::getChar(num_t)’ declared in base class
     virtual int getChar(num_t Port);
                 ^~~~~~~

Да и если прописать static в сам базовый класс, то тоже будет ошибка что использование virtual static недопустимо.

Вот. Как обойти эту проблему? Возможно, есть какое-то стандартное решение, о котором в силу отсутствия опыта я не знаю?

★★★★★

class AbstractChannel : public QObject
    virtual int getChar(num_t Port) = 0;
.....
class FileChannel : public AbstractChannel
    int getChar(num_t Port);
.....
int FileChannel::getChar(num_t Port)
{
    ......
}

???

deep-purple ★★★★★
()
virtual int getChar(num_t Port)

Почему именно такая сигнатура, а не virtual int getChar(), где num_t Port преполагается полем класса (не статическим)?

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Почему именно такая сигнатура, а не virtual int getChar(), где num_t Port преполагается полем класса (не статическим)?

Потому что парсер - это сторонняя библиотека. Так решил ее автор, и менять это дело никто не будет. В том что есть/нет параметра особой проблемы нет.

Вопрос в том, как сделать виртуальный метод статическим или придумать что-то на замену.

Xintrea ★★★★★
() автор топика

В С++ нельзя делать статическими виртуальные методы

«Nor can a virtual function be a static member, since a virtual function call relies on a specific object for determining which function to invoke.»

jo_b1ack ★★★★★
()
Ответ на: комментарий от Xintrea

Потому что парсер - это сторонняя библиотека. Так решил ее автор, и менять это дело никто не будет. В том что есть/нет параметра особой проблемы нет

Тогда не очень понятно, что же такое объект класса FileChannel. Порт он не хранит, а что тогда хранит? Чем отличаются между собой два объекта класса FileChannel?

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

И действительно, тогда просто пусть запилит себе базовый класс с публичными методами заглушками и тупо переопределит их в наследниках. Хотя я по сути то же самое ему показал с виртуальным.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

Вот смотри, этот парсер при ините запоминает указатели на методы обратного вызова:

    Parser->GetChanApi().onCHAN_Getc = channel->getChar;
    Parser->GetChanApi().onCHAN_IsFileDef = channel->isFileDef;

Поэтому все методы getChar()/isFileDef() и проч. должны быть статическими.

На сам парсер я повлиять не могу.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

Тогда не очень понятно, что же такое объект класса FileChannel. Порт он не хранит, а что тогда хранит? Чем отличаются между собой два объекта класса FileChannel?

Порт - это виртуальный порт внутри канала. Это порт на уровне протокола. Он используется внутри парсера для разделения потоков, но в моем случае это номинальный параметр, он просто должен быть, ибо так устроен парсер.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Не очень ясно почему они должны быть статическими... Скинь как декларирование

jo_b1ack ★★★★★
()
Ответ на: комментарий от Xintrea

А с хрена у тебя один инстанс парсера?

И что в доках парсера так и написано — присваивай ему свои методы в рантайме? Или это ты так накостылил?

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от deep-purple

пусть запилит себе базовый класс с публичными методами заглушками и тупо переопределит их в наследниках

Я так сейчас сделал, оно работает. Но только теперь нет контроля что метод будет переопределен, как это происходит при использовании virtual.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

FileChannel это просто интерфейс (класс без полей) или всё же нет?

Нет. У FileChannel куча еще дополнительных методов, всякие open, close, reopen и т.д. Когда вызывается его getChar(), то этот метод по сути, берет данные из файла.

У TcpChannel тоже куча дополнительных методов. Когда вызывается его getChar(), то этот метод по сути, берет данные из TCP-соединения.

И т.д.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от deep-purple

Я же тебе с виртуалом в самом первом месадже пример написал.

Я так делал, у меня не работает. Имеешь в виду что достаточно было =0 прописать?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Parser->GetChanApi().onCHAN_Getc = channel->getChar;

Больше похоже на обычные указатели на функции, хранимые в структуре, а не на методы. Показывай остальной код.

Deleted
()

Для правильного ответа на ваш вопрос достаточно вспомнить,
что указатель на функцию отличается от указателя на метод класса. подробности:
https://isocpp.org/wiki/faq/pointers-to-members
чем отличается static от обычного? -> static имеет доступ только к static переменным класса (т.е. он не требует дополнительно this указатель на объект).

anonymous
()
Ответ на: комментарий от deep-purple

Человеку нужен указатель на метод класса, который он не сможет засунуть в указатель на функцию.
Ты же ему советуешь определить чисто виртуальную функцию.
проблема в том, что внешний API ждет указатель на функцию:
Коллега же пытается запихать туда указатель на метод, который по определению не явно содержит указатель this

anonymous
()

Никак этого не сделать, потому что

  1. Виртуальный метод, как здесь уже сказали, не может быть статическим. А у не статических методов другая сигнатура, т. к. первым идёт неявный указатель this, имеющий тип твоего класса.
  2. Даже если бы каким-то образом можно было сделать виртуальный метод статическим, это тебе не помогло бы, т. к. таблица виртуальных функций хранится в твоём классе, а используемая тобой библиотека хранит указатели у себя, а не обращается к внешним таблицам.

Но если интерфейс самой библиотеки тоже реализован через классы, то ты можешь создать наследников библиотечных классов, в которых эти указатели будут переопределяться. Либо можно создать несколько библиотечных объектов (если интерфейс и логика программы позволяют это сделать) и каждый такой объект инициализировать отдельно в конструкторе соответствующего класса. Наконец, если вызовы коллбаков не асинхронные и вызываются в результате каких-то явных вызовов в твоей программе, а сама программа однопоточная, то можно каждый раз подставлять указатель на нужный метод в виртуальных методах твоих производных классов. Чтобы ответить конкретнее, надо знать, что за библиотеку ты используешь и иметь доки, спеки и заголовки этой библиотеки.

aureliano15 ★★
()
Ответ на: комментарий от deep-purple

А с хрена у тебя один инстанс парсера?
И что в доках парсера так и написано — присваивай ему свои методы в рантайме? Или это ты так накостылил?

Один инстанс не парсера, а канала. И то только потому, что парсеру надо устанавливать методы обратного вызова, и поэтому они должны быть static.

Да, в официальной документации именно так настройка парсера и происходит.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Покажи структуру, которую отдает `Parser->GetChanApi()` и тип переменной `Parser->GetChanApi().onCHAN_IsFileDef`

Stil ★★★★★
()
Ответ на: комментарий от Deleted

Код показать не могу, библиотека закрытая. Ее пишет любитель Си, который вынужден писать на плюсах, потому она страшненькая.

Там устанавливаются указатели на функции, поэтому методы и должны быть статическими.

Xintrea ★★★★★
() автор топика

Эта библиотека не умеет в callback передавать пользовательские данные. Обычно какой-нибудь void* user_data. Если умеет, то через них ты можешь передать указатель на класс и вызвать виртуальный метод.

ox55ff ★★★★★
()
Ответ на: комментарий от i-rinat

Это смотря как объявлен указатель на функцию. Если сишным способом void (*onCHAN_Getc) (int Port), то туда лямбду с замыканием передать нельзя.

ox55ff ★★★★★
()
Ответ на: комментарий от Xintrea

Каждый раз когда тебе нужен новый сишный колбэк для метода класса, а поддержки указателя на пользовательские данные в либе нет:

1. Выделяешь mmap-ом кусок памяти с разрешением исполнять код.
2. Записываешь туда машкод вызова метода класса. Адрес метода и указатель на объект класса прописываешь в машкоде.
3. Передаёшь указатель на начало машкода как адрес функции.

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

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 1)
Ответ на: комментарий от ox55ff

В сях колбеки принимают ещё void *ctx, так что можно использовать лямбды без замыканий. Если нет... то никак.

i-rinat ★★★★★
()
Ответ на: комментарий от Xintrea

Там устанавливаются указатели на функции, поэтому методы и должны быть статическими.

Значит ты хочешь странного. Вернее, твой любитель си хочет странного, если пишет на крестах с сишными подходом. Объяви обычные функции и передавай туда.

Статический виртуальный метод - это самопротиворечивое высказывание, типа как горячий холод или мокрая засуха.

Deleted
()

шибко умный дом штоле делаешь? ComChannel переименуй в Serial, не позорься. TCP бывает сервер и клиент, так что два нужно (или тоже переименуй на будущее).

anonymous
()
Ответ на: комментарий от anonymous

И вообще ты неправильно делаешь. Все эти «каналы» должны писать в унифицированный поток/буфер (не знаю как ты реализовал). И твоя проблема с абстрактным методом сама уйдёт.

anonymous
()
Ответ на: комментарий от anonymous

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

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Печально. Тогда придётся делать по хардкору. Создай две сишные функции и скорми их своему парсеру. Указатель на AbstractChannel помести в глобальную переменную и дёргай его виртуальные методы из этих самых сишных функций. Выглядит какашно, но работать будет.

ox55ff ★★★★★
()
Ответ на: комментарий от deep-purple

Есть разница. Если лямбда захватывает что-то в замыкание, то её уже нельзя дёрнуть как сишную функцию.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Но у меня еще есть второй вариант - инитить парсер пятью указателями в Си стиле (на каждый статический метод класса). Я то хотел от этого отойти, просто передавая указатель на объект канала, чтоб парсер сам подключился к нужным методам, нт видимо так не получится.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Просто ты пишешь на C++, как на C. Всю логику управления перенеси в канал, а в парсере заведи свойства состояний. Т.е. твой канал должен проверять, например для TCP, не надо ли ему закрыть соединение. Как он это будет делать (и будет ли делать вообще) - его личное (private) дело. А парсер должен парсить и не более того.

anonymous
()
Ответ на: комментарий от ox55ff

Но можно передать сишную функцию, из которой дергается std::function к которой через std::bind привязан метод класса.

iSage ★★★★
()
Ответ на: комментарий от iSage

Но можно передать сишную функцию, из которой дергается std::function к которой через std::bind привязан метод класса.

Чем это отличается от

Указатель на AbstractChannel помести в глобальную переменную и дёргай его виртуальные методы из этих самых сишных функций.

?

korvin_ ★★★★★
()

С чего это вдруг он должны быть статическими?

invy ★★★★★
()
Ответ на: комментарий от anonymous

В GTK в колбэки передаётся указатель на user-supplied data.

Суть была в том что сишный указатель функцию это не кастыль, а повсеместно используемый паттерн

jo_b1ack ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.