Как создать указатель на указатель на функцию? - PullRequest
5 голосов
/ 29 января 2020

Я пытаюсь создать структуру, которая имеет 2 функции, которые могут быть переписаны при необходимости позже. Функции: onClicked () и onClickedRight (). Код для структуры:

typedef struct {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    void (*onClicked)() = nullptr;
    void (*(*onClickedRight))() = &onClicked; // by default, execute the same function from onClicked()
} ConfigButton;

Как я пытаюсь выполнить эти функции:

ConfigButton b;
...
// test if click funtion has been defined, to execute it
if (b.onClicked)
    b.onClicked(); // this one work just fine

...

if (*(b.onClickedRight))
    (*(b.onClickedRight))(); // this one crashed

Возможно ли это вообще? Я что-то упустил?

Ответы [ 5 ]

2 голосов
/ 29 января 2020

Я думаю, что вы все еще можете решить свою проблему с помощью указателя на указатель на функцию, но это немного неуклюже, так как вы должны вызывать этот указатель-указатель иначе, чем вы делаете с «обычным» указателем на функцию. Вызов будет выглядеть как (*(aButton.onRightClick))(), и вам нужно позволить onRightClick указывать на объект-указатель, указывающий на функцию, а не назначать функцию напрямую.

Я полагаю, вы ищете способ определить это onRightClick по умолчанию «наследует» поведение onClick, если пользователь не переопределит это и не назначит другое поведение для onRightClick. Я вижу два требования, которым должно соответствовать возможное решение:

1) Если onRightClick не было переопределено, оно должно наследовать каждое изменение, сделанное в onClick

2) Если onRightClick переопределяется, отделяется от onClick.

Вы можете решить это с помощью «простых» указателей на функции, назначив onRightClick функцию, которая просто вызывает функцию, назначенную для onClick. Следующий код показывает это для C ++; подход может быть перенесен в C (хотя затем необходимо передать «this» в функцию, вызывающую onClick:

void defaultOnClick() {
    std::cout << "defaultOnClick" << std::endl;
}

void otherClick() {
    std::cout << "otherClick" << std::endl;
}

void rightClick() {
    std::cout << "rightClick" << std::endl;
}


typedef std::function<void(void)> onClickFunctionType;

struct ConfigButton {
    onClickFunctionType onClick = defaultOnClick;
    onClickFunctionType onRightClick = [this](){ this->onClick(); };
} ;

int main() {

    ConfigButton configButton;

    std::cout << "default for both onClick and onRightClick (calling onClick)" << std::endl;
    configButton.onClick();
    configButton.onRightClick();

    std::cout << "special click for onClick; 'inherited' by onRightClick" << std::endl;
    configButton.onClick = otherClick;
    configButton.onClick();
    configButton.onRightClick();

    std::cout << "special click for onClick; different one for onRightClick" << std::endl;
    configButton.onRightClick = rightClick;
    configButton.onClick();
    configButton.onRightClick();

}

Выход:

default for both onClick and onRightClick (calling onClick)
defaultOnClick
defaultOnClick
special click for onClick; 'inherited' by onRightClick
otherClick
otherClick
special click for onClick; different one for onRightClick
otherClick
rightClick
2 голосов
/ 29 января 2020

Когда onClicked является функцией, и &onClicked, и onClicked оценивают одно и то же - указатель на функцию.

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

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

typedef struct {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    void (*onClicked)() = nullptr;
    void (*onClickedRight)() = onClicked;
} ConfigButton;

и

if ( b.onClickedRight)
    b.onClickedRight();
1 голос
/ 31 января 2020

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

struct ConfigButton {
    // ...

    void Fire_OnClicked()
    {
         if ( onClicked )
             onClicked();
    }
    void Fire_OnClickedRight()
    {
         if ( onClickedRight )
             onClickedRight();
         else
             Fire_OnClicked();
    }

private: 
    void (*onClicked)() = nullptr;
    void (*onClickedRight)() = nullptr;
};

Вы можете объединить это с std::function версия, тестирование на пустое вместо того, чтобы требовать, чтобы "пустое" было представлено лямбда-выражением, выполняющим действие по умолчанию. И если есть несколько обработчиков, для которых вы хотите иметь запасной вариант по умолчанию, вы можете уменьшить шаблон, создав шаблонную функцию Fire.


Другой подход, который может сработать, заключается в создании пользовательского типа обработчика с семантикой, аналогичной std::function, но его оператор () выполнит действие по умолчанию, если ни одна функция не была установлена.

1 голос
/ 30 января 2020

Откорректирован ответ, поскольку OP удалил из списка тегов.

Код работает как есть . Таким образом, вы делаете что-то еще неправильно.

Однако использование указателя на указатель на функцию таким способом может не иметь нужной вам семантики. Если структура копируется в другую структуру, элемент onClickedRight в копии не указывает на указатель onClicked в своем собственном экземпляре. Вместо этого он указывает на указатель onClicked исходного экземпляра.

a.onClickedRight = &a.onClicked;
b = a;
assert(b.onClickedRight == &a.onClicked); // Is this intentional?

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

В любом случае, C ++ код не совсем идиоматический c. Для себя я бы, вероятно, использовал виртуальные методы. Синтаксис виртуального метода может легко приспособиться к этому варианту использования.

struct ConfigButton {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    virtual void onClicked() const = 0;
    virtual void onClickedRight () const { onClicked(); }
};

struct Foo : ConfigButton {
    void onClicked () const {
        //...
    }
};

Если вы следуете этому методу, это также будет работать.

0 голосов
/ 30 января 2020

В языковых функциях C указатели являются единственным местом, где имеет смысл скрывать указатели за typedefs

https://godbolt.org/z/Gb_WEy

#include <stdio.h>

typedef int (*fptr_t)();

typedef struct
{
    fptr_t fptr;
    fptr_t *pfptr;
    fptr_t **ppfptr;
    fptr_t ***pppfptr;
}MYSTRUCT_t;

int foo(char *caller)
{
    printf("Function name = %s, caller = %s\n", __FUNCTION__, caller);
    return 0;
}

int main()
{
    MYSTRUCT_t mystr;

    mystr.fptr = foo;
    mystr.pfptr = &mystr.fptr;
    mystr.ppfptr = &mystr.pfptr;
    mystr.pppfptr = &mystr.ppfptr;

    printf("mystr.fptr=%p mystr.pfptr=%p func=%p\n", (void *)mystr.fptr, (void *)mystr.pfptr, (void *)&foo);

    foo("foo");
    mystr.fptr("mystr.fptr");
    (*mystr.pfptr)("mystr.pfptr");
    (*(*mystr.ppfptr))("mystr.ppfptr");
    (*(*(*mystr.pppfptr)))("mystr.pppfptr");
}
...