Указатель на функцию из другого указателя - PullRequest
0 голосов
/ 12 января 2020

Я новичок в c ++ и пытаюсь сделать универсальный c переключатель (т. Е. Устройство, а не оператор C ++), который можно использовать для мигания огней, включения и выключения звуковых сигналов и т. Д. c в моем проекте Arduino.

Я мог бы создать переключаемый интерфейс и реализовать его в классах, которые я хочу "переключить". Но так как я делаю это в целях обучения и вижу способность указателя на функцию в C ++ (это ново для меня, так как я приехал с C# и Java), я жестко думаю, что это была бы хорошая возможность попробуй ...

Проблема в том, что я могу передать функцию в своем коде, только если это локальная функция, но она не будет работать, если я попытаюсь передать функцию из другого объекта, такого как led например.

Некоторый код для иллюстрации проблемы. Это switch.cpp, он получает функции On и Off в своем конструкторе и имеет метод обновления, который вызывается внутри метода l oop в главном классе Arduino ino:

auto_switch.cpp

using switch_function = void(*)();
auto_switch::auto_switch(const switch_function on_function, const switch_function off_function, const int max_speed_count)
{
    //sets all variables...
}

void auto_switch::update(const unsigned long millis)
{
    //turn switch on and off...
}

А это мой файл ino

ino file

#include <Arduino.h>
#include "led.h"
#include "auto_switch.h"

led* main_led;
auto_switch* led_switch;
int slow_speed;

//ugly code
void turn_led_on()
{
    main_led->turn_on();
}

//ugly code
void turn_led_off()
{
    main_led->turn_off();
}

void setup() {
    main_led = new led(2, 3, 4, true, color::white);

    //ugly code
    led_switch = new auto_switch(turn_led_on, turn_led_off, 3);

    slow_speed = led_switch->add_speed(100, 100, 3, 1000);
    led_switch->set_active_speed(slow_speed);
    led_switch->turn_on();
}

void loop() {
    led_switch->update(millis());
}

Он работает, но мне пришлось создать локальную функцию (turn_led_on и turn_led_off), чтобы иметь возможность назначать внутренние функции в качестве параметра для auto_switch конструктор, части, которые я написал //ugly code

Я хотел сделать что-то вроде этого без промежуточного кода:

//doesn't work
led_switch = new auto_switch(main_led->turn_on, main_led->turn_off, 3);

Возможно ли это? Я читал кое-что о stati c указателе на функцию и некоторых std-функциях, которые помогают с этим, если я правильно понял, в этом случае необходим клейкий код, чтобы компилятор мог знать, откуда поступают функции, я думаю ( из какого объекта), но так как функции, которые мне нужно вызывать, не могут быть установлены c Я отказался от этой опции, и функции std, которые, я считаю, не могут использоваться с Arduino или могут, но не должны из-за ограничений производительности ...

В любом случае, имеет ли это смысл, можно ли это сделать с помощью указателя на функции или я должен создать интерфейс или что-то другое?

1 Ответ

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

Прежде чем решить, как это сделать, задайте вопрос: что вы хотите сделать и почему. Потому что, возможно, есть лучшие альтернативы, использующие простые идиомы C ++.

Вариант 1: специализация с полиморфизмом

Вы хотите специализировать некоторые функции вашего коммутатора, поэтому вместо вызова функции auto_switch Вы бы назвали купол более специализированным?

В этом случае вы не сделаете:

//doesn't work
led_switch = new auto_switch(main_led->turn_on, main_led->turn_off, 3);

, но вместо этого вы будете полагаться на полиморфизм с виртуальными функциями в базовом классе:

class auto_switch {
...
    virtual void turn_on();  
    virtual void turn_off(); 
...
}; 

и писать специализированный класс для светодиодов:

class led_witch : public auto_switch {
...
    void turn_on() override;  
    void turn_off() override; 
...
}; 

На самом деле, компилятор будет генерировать некоторые указатели на функции за сценой, но вам не нужно заботиться:

auto_switch s1=new auto_switch(...); 
auto_switch s2=new led_switch(...);   // no problem !! 

s1->turn_on();     // calls auto_switch::turn_on() 
s2->turn_on();     // calls led_switch::turn_on() since the real type of s2 is led_switch 

Но событие если поведение каждого объекта является динамическим c на основе реального класса объекта, объекты того же класса имеют поведение, которое было предопределено во время компиляции. Если это не так, go до следующего варианта.

Вариант 2: указатель на функцию-член

Функции других объектов можно вызывать только с этим объектом под рукой. Таким образом, наличие указателя на функцию led недостаточно: вам также необходимо знать, к какому индикатору он будет применен.

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

Вот упрощенная версия auto_swith: я предоставляю функцию, но также указатель на объект, для которого должна быть вызвана функция:

class auto_switch{
    void (led::*action)(); 
    led *ld; 
public: 
    auto_switch(void(led::*a)(), led*l) : action(a), ld(l) {}
    void go () { (ld->*action)(); }
}; 
// usage:  
auto_switch s(&led::turn_off, &l1);
s.go(); 

Онлайн-демонстрация

Вариант 3: функциональный способ (может, это то, что вы ищете?)

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

class auto_switch{
    std::function<void()> action; 
public: 
    auto_switch(function<void()>a) : action(a) {}
    void go () { action(); }
}; 

Здесь вы можете связать что угодно: любую функцию любого класса:

auto_switch s(bind(&led::turn_off, l1));
s.go(); 
auto_switch s2(bind(&blinking_led::blink, l2));
s2.go(); 

Онлайн-демонстрация

Опция 4: шаблон команды

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

...