Ошибка понимания при использовании адреса нестатического члена для формирования указателя на функцию-член?

#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 .