Возник следующий вопрос: в С есть структура с указателями на функции (могут иметь нулевое значение)
struct {
void (fn1*)(int);
void (fn2*)(int);
}
Некая процедура получает указатель на структуру, проверяет не равен ли нулю указатель на функцию и вызывает ее.
Все просто и удобно. Есть желание преобразовать структуру в класс: создаем базовый класс с пустыми виртуальные функции и класс наследник в котором переопределяем необходимые функции
class base{
virtual void fn1(int) {}
virtual void fn2(int) {}
};
class deriv{
void fn1(int) { ... }
};
Непонятен один момент: как определить что функция fn2 не была переопределена, т.е. вызывать ее не нужно и не нужны подготовительные операции.
Рассматривал вариант с указателем на член класса, но, как выяснилось, сравнивать такие указали нет возможности.
Может есть красивый вариант решения задачи или я все усложняю?
A_>Непонятен один момент: как определить что функция fn2 не была переопределена, т.е. вызывать ее не нужно и не нужны подготовительные операции. A_>Рассматривал вариант с указателем на член класса, но, как выяснилось, сравнивать такие указали нет возможности. A_>Может есть красивый вариант решения задачи или я все усложняю?
А что именно нужно? Гарантировать, что функция переопределена? Или делать какое-то специфическое поведение, в случае если не переопределена?
Если первое, то надо делать функцию чистовиртуальной.
Если второе, то надо в версии функции в базе делать это специфическое поведение...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
A_>Некая процедура получает указатель на структуру, проверяет не равен ли нулю указатель на функцию и вызывает ее. A_>Все просто и удобно. Есть желание преобразовать структуру в класс: создаем базовый класс с пустыми виртуальные функции и класс наследник в котором переопределяем необходимые функции
A_>
A_>Непонятен один момент: как определить что функция fn2 не была переопределена, т.е. вызывать ее не нужно и не нужны подготовительные операции. A_>Рассматривал вариант с указателем на член класса, но, как выяснилось, сравнивать такие указали нет возможности. A_>Может есть красивый вариант решения задачи или я все усложняю?
Ну есть подозрение, что усложняешь) По крайней мере из твоего описания не очевидно, наследование и виртуальные функции это действительно подходящее решение... Это накладные расходы на вызов функций и дополнительный указатель на VTBL в каждом объекте класса. Может стоит другой вариант рассмотреть?
Здравствуйте, Andy_sh, Вы писали:
A_>Все просто и удобно. Есть желание преобразовать структуру в класс: создаем базовый класс с пустыми виртуальные функции и класс наследник в котором переопределяем необходимые функции
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, Andy_sh, Вы писали:
A_>>Непонятен один момент: как определить что функция fn2 не была переопределена, т.е. вызывать ее не нужно и не нужны подготовительные операции. A_>>Рассматривал вариант с указателем на член класса, но, как выяснилось, сравнивать такие указали нет возможности. A_>>Может есть красивый вариант решения задачи или я все усложняю?
E>А что именно нужно? Гарантировать, что функция переопределена? Или делать какое-то специфическое поведение, в случае если не переопределена?
E>Если первое, то надо делать функцию чистовиртуальной. E>Если второе, то надо в версии функции в базе делать это специфическое поведение...
Если функция не переопределена, то вызвать ее не нужно (в базовом классе она является заглушкой). Кроме того, вызову функции могут предшествовать некие действия, как вычисление аргумента, а если функция не переопределена, то и вычисления не нужны.
A_>>Непонятен один момент: как определить что функция fn2 не была переопределена, т.е. вызывать ее не нужно и не нужны подготовительные операции. A_>>Рассматривал вариант с указателем на член класса, но, как выяснилось, сравнивать такие указали нет возможности. A_>>Может есть красивый вариант решения задачи или я все усложняю? МР>Ну есть подозрение, что усложняешь) По крайней мере из твоего описания не очевидно, наследование и виртуальные функции это действительно подходящее решение... Это накладные расходы на вызов функций и дополнительный указатель на VTBL в каждом объекте класса. Может стоит другой вариант рассмотреть?
Была идея инкапсулировать данные и функции в классе. Программа работает указателем на базовый класс, отсюда виртуальные функции. В текущем варианте получается, что усложняю...
Здравствуйте, Sni4ok, Вы писали:
S>Здравствуйте, Andy_sh, Вы писали:
A_>>Все просто и удобно. Есть желание преобразовать структуру в класс: создаем базовый класс с пустыми виртуальные функции и класс наследник в котором переопределяем необходимые функции
S>если всё просто и удобно, то зачем менять?
Здравствуйте, Andy_sh, Вы писали:
A_>Если функция не переопределена, то вызвать ее не нужно (в базовом классе она является заглушкой). Кроме того, вызову функции могут предшествовать некие действия, как вычисление аргумента, а если функция не переопределена, то и вычисления не нужны.
А функций много? В смысле не реализаций, а функций в интерфейсе?
Если не много, то можно пойти по такому пути, что для каждой функции завести свой интерфейс, и выводиться только из тех интерфейсов, которые поддерживаешь. Ну а на клиентской стороне можно по dynamic_cast получать нужный интерфейс и в зависимости от этого действовать
Дополнительный плюс -- можно объединять функции в группы.
struct ICommon { virtual ~ICommon() {} }; // Общий предок, через который передаётся владение и т. п.struct IFunction1 { virtual void F1() = 0; };
struct IFunction2 { virtual void F2() = 0; };
struct IFunction3 { virtual void F3_1() = 0; virtual void F3_2() = 0;};
class ImplA : public ICommon, public IFunction1, public IFunction3 {
// тут реализация F1, F3_1 и F_2
};
void callFXXX( ICommon* p )
{
if( IFunction1* p1 = dynamic_cast<IFunction1*>( p ) ) {
p1->F1();
}
if( IFunction2* p2 = dynamic_cast<IFunction2*>( p ) ) {
p2->F2();
}
if( IFunction3* p3 = dynamic_cast<IFunction3*>( p ) ) {
p3->F3_1();
p3->F3_2();
}
}
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Если не много, то можно пойти по такому пути, что для каждой функции завести свой интерфейс, и выводиться только из тех интерфейсов, которые поддерживаешь. Ну а на клиентской стороне можно по dynamic_cast получать нужный интерфейс и в зависимости от этого действовать E>Дополнительный плюс -- можно объединять функции в группы.
Отличный вариант! Спасибо, возьму на заметку.
Но функций много, громоздко получится.
Здравствуйте, Andy_sh, Вы писали:
A_>Если функция не переопределена, то вызвать ее не нужно (в базовом классе она является заглушкой). Кроме того, вызову функции могут предшествовать некие действия, как вычисление аргумента, а если функция не переопределена, то и вычисления не нужны.
Если функция-заглушка, то вызов и невызов её тождественны
А если нужно обеспечить ленивое вычисление, то я вижу несколько подходов:
1) определить ещё одну функцию — признак энергичности вычислений
Здравствуйте, Andy_sh, Вы писали:
A_>Отличный вариант! Спасибо, возьму на заметку.
Всегда пожалуйста!
A_>Но функций много, громоздко получится.
Расскажи подробнее о своей задаче. Может кто ещё чего интересного посоветует...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Andy_sh, Вы писали:
A_>Непонятен один момент: как определить что функция fn2 не была переопределена, т.е. вызывать ее не нужно и не нужны подготовительные операции.
Т.е. когда fn2 не переопределена, то никакого эффекта ее вызов производить не должен. Так? В таком случае делаешь fn1 и fn2 в базовом классе пустыми. Вызов пустых функций не будет иметь никакого эффекта.
Далее, подготовительные действия нужно тоже вынести в виртуальную функцию, которая будет либо пустая в базовом классе, либо переопределена в наследуемом.
Здравствуйте, Andy_sh, Вы писали:
A_>Непонятен один момент: как определить что функция fn2 не была переопределена, т.е. вызывать ее не нужно и не нужны подготовительные операции.
Делаем так, чтоб все виртуальные функции базового класса бросали определенное исключение — NotImplemented или что-то в этом роде. А вызывающий код обращение к этим функциям делает внутри бока try-catch, и если ловит данное исключение, значит функция не переопределена.
Можно подумать над красотой дизайна. Например, в базовом классе заменить каждую виртуальную функцию парой функций — закрытой виртуальной и открытой невиртуальной (идиома NVI). Тогда ловлю исключений можно спрятать внутри невиртуальных функций-оберток и избавить от этой заботы клиентский код. И второе преимущество — классы-наследники не смогут по ошибке вызывать виртуальные функции базового класса, поскольку они закрыты. Осталось только додумать семантику невиртуальных функций оберток.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Делаем так, чтоб все виртуальные функции базового класса бросали определенное исключение — NotImplemented или что-то в этом роде. А вызывающий код обращение к этим функциям делает внутри бока try-catch, и если ловит данное исключение, значит функция не переопределена.
Здравствуйте, Andy_sh, Вы писали:
A_>Некая процедура получает указатель на структуру, проверяет не равен ли нулю указатель на функцию и вызывает ее. A_>Все просто и удобно. Есть желание преобразовать структуру в класс: создаем базовый класс с пустыми виртуальные функции и класс наследник в котором переопределяем необходимые функции
Описанный тобой алгоритм обладает, на мой взгляд, серьезным недостатком: анализ того, была ли функция определена или нет, выпоняется во время выполнения программы. Лично я бы обязательно постарался переместить этот анализ на этап компиляции. В решении этой задачи может помочь такая мощная возможность языка C++ как шаблоны, при помощи которых к полиморфизму времени выполнения (виртуальным функциям) можно добавить полиморфизм времени компиляции. Но без более детального знания задачи давать конкретные советы трудно.
--
Справедливость выше закона. А человечность выше справедливости.
Andy_sh wrote:
Все просто и удобно. Есть желание преобразовать структуру в класс: > создаем базовый класс с пустыми виртуальные функции и класс наследник в > котором переопределяем необходимые функции > > class base{ > virtual void fn1(int) {} > virtual void fn2(int) {} > }; > > class deriv{ > void fn1(int) { ... } > };
Как бы в той структуре функции -- это данные.
Переопределить в наследнике данные невозможно.
А поскольку физическая реализация виртуальных функций не
определена (может быть любой), то накладывать её на уже
определённую структуру вряд ли получится.
> Непонятен один момент: как определить что функция fn2 не была переопределена, > т.е. вызывать ее не нужно и не нужны подготовительные операции.
Средствами С++ это невозможно. Можно делать пары функций -- одну говорящуу
что операция (пере)определена, другую -- саму реализацию операции.
Я думаю, есть резон не решать это языковыми методами, а подыскать какой-нибудь
подходящий паттерн проектирования.
Здравствуйте, rg45, Вы писали:
R>Делаем так, чтоб все виртуальные функции базового класса бросали определенное исключение — NotImplemented или что-то в этом роде. А вызывающий код обращение к этим функциям делает внутри бока try-catch, и если ловит данное исключение, значит функция не переопределена.
R>Можно подумать над красотой дизайна. Например, в базовом классе заменить каждую виртуальную функцию парой функций — закрытой виртуальной и открытой невиртуальной (идиома NVI). Тогда ловлю исключений можно спрятать внутри невиртуальных функций-оберток и избавить от этой заботы клиентский код. И второе преимущество — классы-наследники не смогут по ошибке вызывать виртуальные функции базового класса, поскольку они закрыты. Осталось только додумать семантику невиртуальных функций оберток.
Ей-ей, лучше остаться со структурами указателей на функции!!! Работать будет БЕСКОНЕЧНО быстрее
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском