typedef void (FunctionSet::* Function)();
class MyFunctionSet : public FunctionSet
{
protected:
void addFunctions()
{
addFunction(Function(&MyFunctionSet::function1));
}
void function1()
{
// Do something.
}
};
Метод addFunction добавляет функцию в список базового класса
.
которые затем можно перечислить для вызова всех функций.
Есть ли способ упростить (меньше печатать) добавление функций?
Некоторый контекст был бы неплохим. Какую проблему вы пытаетесь решить? Где / как это будет использоваться?
ну, в комментариях к одному ответу вы заявили, что «хотите, чтобы все было эффективно». Я думаю, нам нужно знать об этом больше, чтобы дать вам более точные ответы. Вы можете хранить объекты функций, но тогда вам также нужно сохранить указатель this.





Полагаю, вы могли бы перегрузить оператор сложения.
Можете ли вы объяснить, чего вы этим пытаетесь достичь?
Это похоже на довольно плохой дизайн, разве у вас не может быть абстрактный класс («интерфейс C++»), такой как «Computable», который имеет чистую виртуальную функцию1, подкласс Computable для каждой реализации, а затем MyFunctionSet поддерживает набор Computable?
Есть ли конкретная причина, по которой вы используете указатели на функции?
Функторы обычно эффективнее более, чем указатели на функции. Но это когда они реализуются без виртуальных функций. В любом случае «создание классов» - это ерунда, и это не требует дополнительных затрат. Для меня это пахнет преждевременной оптимизацией.
Что именно вы делаете, чтобы эффективность была важнее хороших принципов объектно-ориентированного проектирования? Динамическое связывание так же эффективно, как и ручное управление указателями ваших собственных функций.
Думаю, это вопрос стиля и самого проекта. Указатели на функции в C++ опасны. Если вас беспокоит экономия на вводе текста, используйте лучшую IDE или инструмент CASE.
На одной из моих работ мы представляли всю компьютерную систему вплоть до небольших регистров и форматов инструкций с иерархией классов, и мы были очень счастливы, что Роуз выполняла за нас всю рутинную работу.
Кроме того, IMHO, если это производственный код, который могут поддерживать другие, вы не хотите использовать языковые функции, которые требуют программирования выше среднего для понимания и поддержки. Если это личный код, другая история.
Похоже, вы назначаете указатель функции-члена на функцию производного класса указателю функции-члена на функцию базового класса. Что ж, это запрещено, потому что это открывает дыру в системе типов. Это стало неожиданностью (по крайней мере, для меня, когда я впервые это услышал). Прочтите этот ответ, чтобы узнать почему.
Чтобы ответить на ваш актуальный вопрос - я бы сделал addFunction шаблоном:
void addFunctions() {
addFunction(&MyFunctionSet::function1);
}
Измените addFunction в базовом классе на это:
template<typename Derived>
void addFunction(void(Derived::*f)()) {
myFunctions.push_back(static_cast<Function>(f));
}
Лучше использовать static_cast, потому что он скажет вам, действительно ли Derived не является производным от FunctionSet.
Вероятно, нет способов удалить части & или MyFunctionSet ::?
нет, никак: / подождите, я имею в виду, если вы назначаете указатель без регистра - тогда это запрещено. звучит так, как будто вы это ошиблись, я не хотел говорить, что ваш код запрещен C++. извините, если это звучит немного непонятно :)
Итак, я склонен в некоторой степени не согласиться с Ури.
Если вы реализуете паттерн Observer, вам нужно иметь возможность сказать:
«Когда объект X выполняет Y, я хочу выполнить код Z».
Использование подхода, основанного исключительно на абстрактном классе, - не лучший способ сделать это. Требовать отдельный класс (особенно в C++) для каждого обработчика событий - это излишне. Если вы пришли с Java, они все так и делают. Однако в Java есть 2 функции, которые делают это лишь слегка раздражающим: анонимный класс и классы-члены «экземпляра». Это позволяет вам определять несколько обработчиков для нескольких событий в одном классе. Вы должны добавить к методам префикс "class {", но вы можете это сделать.
В C++ нет ни анонимных классов, ни классов-членов-«экземпляров». Это делает использование абстрактного класса для событий гораздо более громоздким, если у вас есть несколько обработчиков, которым необходимо совместно использовать состояние. Вы должны выполнить аналог создания замыкания вручную, что довольно быстро может раздражать.
.NET использует делегаты для обработки событий, которые в основном представляют собой указатели на функции с типобезопасностью. Они делают код для обработки событий очень простым. Однако, в отличие от указателей функций в C++, делегат объединяет указатели функций-членов и указатели статических функций. По сути, он может каррировать параметр «this» любой функции-члена, оставляя указатели на функции, которые выглядят так же, как указатель статической функции. Это означает, что тип объекта, обрабатывающего событие, не является частью «интерфейса» события. Это делает его очень гибким.
Вы не можете сделать это напрямую, используя указатели функций-членов C++, потому что тип «this» в конечном итоге становится частью типа указателя функции, тем самым ограничивая ваши обработчики только появлением в текущем классе.
Лучшее решение для C++ - это их гибрид. Вам нужен такой общий интерфейс:
class EventHandler
{
public:
virtual void Handle() = 0;
};
А затем реализация для таких функций-членов
template <class T>
class MemberFuncEventHandler : public EventHandler
{
public:
MemberFuncEventHandler(T * pThis, void (T::*pFunc)()) : m_pThis(pThis), m_pFunc(pFunc)
{
}
void Handle()
{
(m_pThis->*m_pFunc)();
}
private:
T* m_pThis;
void (T::*m_pFunc)();
};
template <class T>
EventHandler * Handler(T * pThis, void (T::*pFunc)())
{
return new MemberFuncEventHandler<T>(pThis, pFunc);
}
Вы также можете аналогичным образом определить класс обработчика для статических методов. Тогда вы можете делать что-то вроде этого:
Handlers += Handler(obj, &(c1::foo));
Handlers += Handler(obj, &(c2::bar));
Handlers += Handler(blaa); // for static methods...
Создание отдельного класса не является излишним с точки зрения дизайна, только с точки зрения набора текста (в конце концов, Java и C# создают классы, когда вы используете анонимность). Я считаю, что инструмент CASE или хорошая IDE могут сэкономить эту работу, сохранив при этом хороший дизайн.
Хотя мне очень нравится ваш код, я сомневаюсь, что средний (не зависимый от SO) программист на C++ сможет правильно его понять и поддерживать и, следовательно, с осторожностью будет использовать его в производстве.
Не уверен, что код такой сложный ... Написание заняло у меня около 5 минут. Большую часть времени нужно было просто уточнить синтаксис указателя на функцию-член. Я думаю, что есть реализации в boost (например, кто-то из упомянутых ниже), которые можно просто загрузить, и, вероятно, скоро они будут в стандарте.
Uri, по поводу инструмента CASE ... Свое мнение по этому поводу я написал в своем блоге. Вы можете прочитать это на specbug.com/blog/2009/1/21/scotts-law-of-software-2.html
Если вы пытаетесь создать систему обратного вызова или что-то еще, я бы порекомендовал библиотеку Boost.Signals. По сути, он объединяет функции и вызывает группу функций по команде (как и любая другая библиотека сигналов), но он также предназначен для работы с Boost.Bind и Boost.Function, которые великолепны.
Пример:
using boost::function
using boost::bind
using boost::signal
void some_other_func();
Foo a;
Bar b;
signal<void()> sig;
sig.connect(bind(&Foo::foo,&a));
sig.connect(bind(&Bar::bar,&b));
sig.connect(some_other_func);
sig(); // calls -> a.foo(), b.bar(), some_other_func()
Также поддерживает блокировку, сложное управление подключением и другие полезные вещи.
Я привел более сложный пример. Если вы посмотрите документацию Boost.Signals, вы увидите, что это действительно приятный минималистичный интерфейс. Для типобезопасных универсальных обратных вызовов я не видел лучшей библиотеки.
Не могли бы вы изменить название? Когда вопросы звучат так банально, иногда просто кажется, что это ловушка, чтобы заставить людей прийти и посмотреть (и да, я был в ловушке!) Описательные названия вопросов гораздо более уместны. Что касается фактического ответа, я полагаю, мне нужно освежить свой C++;)