#c #member-function-pointers
#c #указатель на элемент
Вопрос:
У меня проблема, которую я не уверен, как обойти.
Я использую библиотеку, экземпляры класса которой создаются путем передачи ссылок на несколько функций.
void func_on_enter() {...}
void func() {...}
void func_on_exit(){...}
State state_a(amp;func_on_enter, amp;func, amp;func_on_exit);
Я пишу класс, который содержит экземпляр этого класса, и поэтому попытался сделать что-то вроде этого:
class MyClass{
private:
void func_on_enter() {...}
void func() {...}
void func_on_exit(){...}
State _state_a;
public:
MyClass() : _state_a(amp;func_on_enter, amp;func, amp;func_on_exit) {}
};
Я получаю ошибку «ISO C запрещает использовать адрес неквалифицированной или заключенной в скобки нестатической функции-члена для формирования указателя на функцию-член. Скажите ‘amp;MyClass::_func_on_enter'» (и, очевидно, то же самое для двух других методов). Компилятор услужливо предоставляет решение, которое я попробовал.
Однако затем компилятор напоминает мне, что нет соответствующей функции для вызова 'State::State(void (MyClass::*)(), void (MyClass::*)(), (MyClass::*)())'
и что у нее нет известного преобразования из 'void (MyClass::*)()'
в 'void(*)()'
.
Я пытался прочитать похожие вопросы об этой ошибке, но, похоже, не могу найти решение, которое я понимаю и могу помочь в этой ситуации.
Я рассматривал возможность добавления конструктора перегрузки в State, но это кажется абсурдным для каждого класса в отдельности, учитывая, что я мог бы вызывать его из любого другого класса.
Я также некоторое время думал, что мог бы просто сделать функции статическими (поскольку они одинаковы для всех экземпляров MyClass), но я быстро вспомнил, что тогда они не смогут использовать нестатические переменные-члены MyClass.
Возможно, есть способ, которым я могу обеспечить преобразование или предоставить указатели для конструктора состояния другим способом?
Мне действительно не помешала бы некоторая помощь в этом, поскольку я совершенно зациклен на идеях о том, как двигаться дальше. Любая помощь или подсказки были бы высоко оценены!
Комментарии:
1. Вы контролируете
State
класс? Если это так, переключитесь с необработанныхvoid(*)()
указателей наstd::function<void()>
или шаблонState
в зависимости от типа обратных вызовов. Если это не так, то то, что вы хотите, может быть трудным до невозможного.2. Простое решение (которое может быть неприменимо к вам) — просто сделать
func
etc статичным.3. @MilesBudnek Я могу контролировать класс State — я уже разветвил библиотеку, из которой он получен, чтобы добавить больше функций. Я полагаю, мне нужно напомнить себе о внутренней работе State, чтобы увидеть, потребует ли это от меня переписать (или шаблон) весь класс или я могу обойтись перегрузкой конструктора для принятия
std::function<void()>
. Я не очень знаком сstd::function
, правильно ли я понимаю, что они могут использоваться аналогично указателям для косвенного вызова функции? Если это так, я вижу, что это решение!4. @john определенно может быть полезным для других, но, к сожалению, не в моем случае
Ответ №1:
Указатели на нестатические функции-члены не могут быть преобразованы в простые указатели на функции. Для работы с A void (MyClass::*)()
требуется MyClass
объект. Однако необработанные указатели не могут содержать какое-либо состояние, поэтому у a нет возможности void(*)()
сохранить информацию о том, с каким MyClass
объектом работать.
К счастью, в стандартной библиотеке есть шаблон класса, предназначенный именно для этой цели: std::function
. std::function
является более или менее заменой необработанных указателей на функции, но он может содержать любой тип вызываемого объекта, совместимый с заданной сигнатурой. Это означает, что он может сохранять состояние, необходимое для обратного вызова определенного экземпляра класса:
class State {
private:
std::function<void()> on_enter;
std::function<void()> func;
std::function<void()> on_exit;
public:
State(std::function<void()> on_enter, std::function<void()> func, std::function<void()> on_exit)
: on_enter{std::move(on_enter)},
func{std::move(func)},
on_exit{std::move(on_exit)}
{}
void do_something() {
on_enter();
// stuff
func();
// more stuff
on_exit();
}
};
class MyClass {
private:
void func_on_enter() {}
void func() {}
void func_on_exit(){}
State state_a;
public:
MyClass()
: state_a([this]() { func_on_enter(); },
[this]() { func(); },
[this]() { func_on_exit(); })
{}
};
Здесь, вместо того, чтобы передавать указатели на ваши функции-члены напрямую, я обернул их в лямбды, которые фиксируют this
указатель объекта, подлежащего обратному вызову. Теперь State
объект может вызывать эти элементы, и они знают, с каким экземпляром MyClass
работать.
Однако будьте осторожны с временем жизни объекта. Поскольку state_a
содержит указатели обратно на свой MyClass
экземпляр, вам нужно убедиться, что MyClass
конструктор копирования / перемещения и операторы присваивания работают правильно. Реализаций по умолчанию, предоставляемых компилятором, недостаточно.
Комментарии:
1. Я предполагаю, что необходимо убедиться, что время жизни данного
State
экземпляра не превышает время жизниMyClass
экземпляра, с помощью которого он был инициализирован. (В данном случае использования, когдаState
является членом данногоMyClass
, это гарантировано.) — Очень хорошее решение.2. @PeterA. Шнайдер, однако, это не гарантируется для копий
MyClass
.State
Копия будет ссылаться на источник копии, срок службы которого, возможно, закончился.3. Несколько вопросов @MilesBudnek: 1. Просматривая ваш пример, единственная часть, которую я не понимаю, — это лямбды. Как они создают
std::function<void()>
для конструктора state_a? Или компилятор неявно преобразует их вstd::function<void()>
s? 2. Если я хочу, например,state_a
ничего не делать вместоfunc()
, мое предыдущее состояние может быть инициировано с помощьюNULL
везде, где я не хотел вызова функции. Могу ли я сделать это с помощью этого метода?4. @JRVeale 1.
std::function<void()>
неявно конструируется из любого вызываемого объекта, который не принимает параметров и возвращаетvoid
, включая лямбды. 2.std::function
не может содержать вызываемого объекта. Используйте либо конструктор без аргументов, либо тот, который принимаетnullptr
. Он также контекстуально преобразуется вbool
, поэтому вы можете проверить, содержит ли он вызываемый объект с чем-то вродеif (func) { func(); }
, точно так же, как вы бы использовали необработанный указатель на функцию.5. Блестяще, спасибо. Я с нетерпением жду реализации этого сейчас
Ответ №2:
void func_on_enter() {...} void func() {...} void func_on_exit(){...} State state_a(amp;func_on_enter, amp;func, amp;func_on_exit);
Чтобы это сработало, конструктор State
должен принимать указатели на функции типа void(*)()
.
_state_a(amp;MyClass::func_on_enter, amp;MyClass::func, amp;MyClass::func_on_exit)
Чтобы это сработало, конструктор State
должен принимать указатели на функции-члены типа void (MyClass::*)()
. Указатели на функции-члены не преобразуются в указатели на функции.
Вы могли бы изменить конструктор State
, чтобы принимать указатели на функции-члены правильного типа.
Комментарии:
1. Это сработало бы, да. Поскольку я, вероятно, буду использовать State и в других классах, я думаю, что использование
std::function<void()>
в State (ss по предложению @MilesBudnik) будет лучшим способом разрешить вызов State в любом классе (а не только MyClass)2. В чем разница между случаем 1 и случаем 2? Не является ли квалификация в случае 2 избыточной в MyClass?
3. Я думаю, что это принципиально игнорирует дизайн
State
. Предполагается, что это работает с автономной функцией. Введение зависимости, напримерMyClass
(и скольких других?) не кажется хорошим дизайнерским решением, даже если OP может изменитьсяState
.4. @PeterA. Schneider 1. Разница в том, что функции в случае 1 не являются нестатическими функциями-членами. Функции в случае 2 являются нестатическими функциями-членами. 2. Квалификация не является избыточной внутри
MyClass
. 3. Предполагаемый дизайнState
не был указан (до комментария к моему ответу, который немного затрагивает), за исключением того, что OP желает передать нестатические функции-члены в конструктор. Иногда нормально иметь зависимости между классами, и некоторые классы могут быть предназначены для использования только с другим классом.5. О, извините, теперь я вижу, что первый приведенный код был просто примером автономной функции. Конечно, они не могут быть указаны с именем класса, и их тип действительно является
void(*)()
. — Что касается «первого примера, показывающего, как работает класс»: это работает таким образом, потому что он был разработан таким образом — без зависимости отMyClass
. Я понимаю, что OP счел бы удобным передавать указатели на функции-члены, но при этом вводится зависимость, которая, по-видимому, не была запланирована в первоначальном проекте.State
это общее имя; это неMyClass::State
.