Pull to refresh

Comments 39

Именно «типа». Какая частота дискретизации при воспроизведении на смартфоне? 44.1 кГц? 48 кГц? 192 кГц? Т.е. генератор на основе смартфона физически не сможет вывести сигнал частотой 100 кГц.
Кроме того, на смартфоне невозможно генерировать PWM сигналы с частотой в мегагерцы.
А ещё в смартфоне на выходе стоит разделительный конденсатор и сигнал там колеблется относительно земли. В моей же «поделке» сигнал колеблется относительно половины напряжения питания и его смело можно подавать на на входы микросхем которые не толерантный к напряжениям вне диапазона 0-3.3 вольта.
Ну, последняя-то как раз проблема решается делителем из пары резисторов + контролем амплитуды, а вот с частотным диапазоном у мобильника и правда беда-беда.
UFO just landed and posted this here
А как вы возьмёте указатель на не статическую функцию? В C++ вы не можете этого сделать. Можно получить указатель только на статические функции класса, но т.к. при вызове статических функций не передается указатель на объект, они могут работать только со статическими членами класса(об этом рассказывают в университете). А что бы работать с остальными им нужна ссылка или указатель на объект(а вот о такой возможности не говорят, да и в книжках прямого указания на это я не встречал).
Есть ещё вариант с callback классом имеющим pure virtual функцию, но там свои нюансы. Например, невозможность использовать несколько функций обратного вызова в одном классе.
UFO just landed and posted this here
Попробовал. Ожидаемо не "взлетает": Error C2509 'Callback': member function not declared in 'Application'.
Работает если сделать так
class ICallback
{
  public:
	virtual void Callback() = 0;
};

class Application : public ICallback
{
  public:
	virtual void Callback();
};

void Application::Callback()
{
  printf("Class Application Callback()\n");
}

class Invoker
{
  ICallback *_callback;
public:
  void SetCallback(ICallback *callback)
  {
    _callback = callback;
  }
  void invoke()
  {
    if (_callback != nullptr)
    {
      _callback->Callback();
    }
  }
};

int main(int s, char** o)
{
  Application app = Application();
  Invoker inv = Invoker();

  inv.SetCallback(&app);
  inv.invoke();

  return 0;
}
Но это как раз вариант с callback классом имеющим pure virtual функцию о котором я говорил выше. И, как вы понимаете, при таком подходе будет только одна callback функция в классе Application.
UFO just landed and posted this here
А разве внутренние классы AlarmHandler и KeyHandler имеют доступ ко всем членам класса Application? В таком случае в Application придется еще публичный интерфейс делать для вызова из этих внутренних классов.
UFO just landed and posted this here
А как вы возьмёте указатель на не статическую функцию? В C++ вы не можете этого сделать.


Строго говоря, можно. Другой вопрос, что указатель на функцию-член без указателя на объект, для которого нужно её вызвать, большого смысла не имеет.

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


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

Я извиняюсь за такой уровень моего занудства, просто я искренне не понимаю, почему Вы преподносите обыденную вещь как магию. Я не помню, когда решение подобной задачи вызывало у меня сложности, при том, что опыта у меня не так уж и много.
Строго говоря, можно. Другой вопрос, что указатель на функцию-член без указателя на объект, для которого нужно её вызвать, большого смысла не имеет.
Как? Можно код для вывода на экран адреса функции-члена класса?
Простейший код:
printf("Ponter to SetCallback(): 0x%08X", &SetCallback);
не компилируется: Error C2276 '&': illegal operation on bound member function expression
Мне кажется, не трудно сопоставить факты, чтобы понять, что статической функции нужна ссылка (в общем смысле) на объект, к нестатическим членам которого необходимо произвести доступ.
Это вам кажется теперь, когда эти факты за вас сопоставлены, сделан вывод, причем все это не противоречит вашим собственным знаниям и опыту. Есть такая штука как инерция мышления — если в университете говорили нельзя, значит нельзя и даже не пытаемся это сделать.
Изначально была идея взять указатель на функцию-член класса, преобразовать её к функции вида void (*callback)(void* ptr, void* param_ptr, uint32_t param); и вызывать передавая в качестве первого указателя указатель на объект. Это не сработало, потому что указатель на функцию-член класса, даже если и можно взять, то нельзя преобразовать ни к чему другому. И вот только после этого до меня дошло попробовать со статической функцией.

В C++ есть указатели на не статические функции классов/структур: https://ideone.com/XiIW9b


Но они не эквивалентны простому адресу начала метода (в примере это видно хотя бы по sizeof). Например так: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-pointers


Изначально была идея взять указатель на функцию-член класса, преобразовать её к функции вида void (callback)(void ptr, void* param_ptr, uint32_t param); и вызывать передавая в качестве первого указателя указатель на объект.

У вызовов методов может быть отдельное соглашение о передаче параметров — https://en.wikipedia.org/wiki/X86_calling_conventions#thiscall

Как? Можно код для вывода на экран адреса функции-члена класса?

Во-первых, я привёл ссылку на авторитетный источник — FAQ на сайте isocpp.org, где показано, как взять адрес функции-члена.

Во-вторых:
std::printf ("Ponter to SetCallback(): 0x%p", &UiButton::SetCallback);

Чтобы не возникло вопросов: да, я проверил такой синтаксис. Единственное что, проверял на своём коде, т.к. скачивать и собирать что-то под STM нет никакого желания. Если функция-член SetCallback принадлежит другому классу — используйте исправьте приведённый выше код.

Это вам кажется теперь, когда эти факты за вас сопоставлены, сделан вывод, причем все это не противоречит вашим собственным знаниям и опыту.

Кто эти факты за меня сопоставлял? Кто делал какие-то выводы? Или Вы считаете, что я увидел использование указателя на объект класса в статической функции-члене того же класса в Вашем коде впервые, и решил, что это очевидно? А ничего, что я использовал точно такой же подход в своём коде задолго до того, как Вы решили написать данную статью?

Есть такая штука как инерция мышления — если в университете говорили нельзя, значит нельзя и даже не пытаемся это сделать.

Я не понимаю, почему Вы так фиксируетесь на том том, что говорили в университете, причём строго на некорректной формулировке. У меня своя голова на плечах есть, чтобы пробовать что-то за пределами того, что давали на парах в университете. Я уж не буду говорить, что на пары по C++ я не ходил, т.к. ничего нового для меня там не рассказывали.
Во-вторых:
std::printf ("Ponter to SetCallback(): 0x%p", &UiButton::SetCallback);
Чтобы не возникло вопросов: да, я проверил такой синтаксис.
Убедили. Похоже можно взять указатель на функцию член и положить её в переменный аргумент-лист(кладется она туда как указатель на функцию член), а потом вынуть как что угодно. Но вот сделать вот так:
std::printf ("Ponter to SetCallback(): 0x%p", (void*)&UiButton::SetCallback);
не получится. Да и не нужно — все решается теми методами, которые тут уже обсудили.

С помощью std::bind. 2018 год на дворе, как никак.

Здравствуйте!

В приведённом коде функции Application::Callback я вижу сразу несколько проблем:
  1. Использование void* для передачи указателей на объекты. Помимо нарушения идеи безопасности на уровне типов, это также усложняет понимание сигнатуры функции. EDIT: Видимо, это вынужденная мера, т.к. UiButton хочет такую сигнатуру функции обратного вызова. Моего бессмысленного недовольства это не уменьшает, но позволяет понять, почему так.
  2. Отсутствие проверки указателей на nullptr. Если данные проверки были опущены для простоты понимания в рамках статьи — стоит указать на это в тексте статьи, чтобы не возникало непонимания. Если же проверок нет, то их разумно добавить, как минимум в виде assert'ов.
  3. Отсутствие проверки типов аргументов. Раз уж передаются произвольные указатели, то необходимо проверять, что это указатели на объекты правильных типов. Простейшим, хотя и не самым эффективным, является использование dunamic_cast(). Более эффективных методов много, требуют они написания различного объёма вспомогательного кода. Но лучшим решением является, разумеется, использование правильных типов параметров функции.

Еще с университетской скамьи я помню постулат: «Статические функции не имеют доступа к не статическим членам класса». Так вот это не соответствует действительности. Поскольку статическая функция является членом класса, то она имеет доступ ко всем членам класса, если имеет ссылку/указатель на этот класс.

Как бы Вам сказать… Это не совсем корректно сформулированный постулат. Идея, заложенная в постулате, заключается в том, что нельзя в статических функциях-членах класса использовать нестатические переменные-члены и функции-члены без объекта данного класса. Поэтому, Вы уж извините, никакой магии Вы не используете.
Если реализуется взаимодействие между С и C++, то такие callbacks с void* opaque pointer являются наистандартнейшим решением. Строго говоря, я не припомню иных вариантов, разве только совсем без void*, но тогда это довольно неприятная картина и начинаются танцы со всякими offsetof вычислениями по другому известному указателю.

Проблемы с callback реализуемыми в с++:
1) с++ в callback должен сам ловить все свои Exceptions, иначе вызывающий С код может работать предельно некорректно — например оставлять занятые мутексы, не освобожденные ресурсы и пр.
2) в общем случае с++ должен сам убедиться, что в указателе именно экземпляр нужного класса. dynamic_cast для этого годится плохо, т.к. dynamic_cast лишь предоставляет «навигацию» по дереву наследования полиморфных классов, а тут на входе вовсе не класс, а указатель на нечто (void*) с которым dynamic_cast работать не должен (допускаю, что возможно какой-то компилятор по ошибке позволяет, но стандарт чётко это запрещает).
С другой стороны, с++ часть может «полагаться» на правильную работу С части и ожидать в ответе именно тот указатель, который был ей передан ранее. Тогда нужно гарантировать что lifetime объекта будет достаточен, до всех вызовов callback с этим классом.
Есть еще вариант — передать указатель на память, в котором разместить признак (просто ID объекта) и указатель на объект. Эта память может быть частью самого объкта, и в деструкторе, если вдруг объект досрочно уничтожается, можно этот ID менять на другой — легче ловить dangling pointers. Правда это вовсе не гарантирует что следующий размещенный объект не будет создан в том же месте кучи.
Здравствуйте!

Если реализуется взаимодействие между С и C++, то такие callbacks с void* opaque pointer являются наистандартнейшим решением.

С этим я полностью согласен. В рамках же рассматриваемого в статье кода взаимодействия между C и C++, насколько я могу судить, не наблюдается, т.к. Application::Callback используется в связке с классом UiButton, т.е. это не C. Насколько я могу судить, весь код написан на C++, и в моём представлении в этом случае лучше использовать механизмы C++.

в общем случае с++ должен сам убедиться, что в указателе именно экземпляр нужного класса. dynamic_cast для этого годится плохо, т.к. dynamic_cast лишь предоставляет «навигацию» по дереву наследования полиморфных классов, а тут на входе вовсе не класс, а указатель на нечто (void*) с которым dynamic_cast работать не должен (допускаю, что возможно какой-то компилятор по ошибке позволяет, но стандарт чётко это запрещает).

Признаю, был неправ. Для проверки типа объекта, размещённого по адресу, хранимому в void*, использовать dynamic_cast нельзя. Произвёл небольшие опыты, которые показали, что dynamic_cast либо не будет работать правильно, либо приложение аварийно завершит работу. С dynamic_cast всё ещё можно что-то сделать, но это потребует передачи указателя на некоторый базовый тип, что, мягко говоря, является сомнительной идеей в общем случае, особенно при взаимодействии между C и C++.

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

В рамках же рассматриваемого в статье кода взаимодействия между C и C++, насколько я могу судить, не наблюдается, т.к. Application::Callback используется в связке с классом UiButton


Да, я видел это, но не смотрел что лежит под UiButton. Если UiButton лишь wrapper вокруг С библиотеки, то могу допустить что интерфейс callback напрямую вытащен наружу для упрощения.
С другой стороны, вы правы — сам UiButton уже С++ и мог бы взять на себя функции взаимодействия с С++ приложением более подобающим для С++ способом. Для этого нужны или шаблоны или полиморфные классы. Т.е. библиотека должна знать API потребителя (приложения) — т.е. вызывать его методы — или совсем не зная его классов, работая через шаблоны, или должна знать лишь базовые классы, которые в ней же в библиотеке реалиованы и которые потребитель отнаследует и реализует соответственно.

Я знаю, что эти объекты будут жить, сколько надо, но как-то неспокойно мне на фоне страшных рассказов

Всякое бывает. Если время жизни не получается легко контролировать, то используется shared_ptr. Решение не идеальное, конечно, но в ряде случаев ничего другого не остается.
1) А как передать указатели на разные объекты в UiButton для последующей передачи в callback функцию? Я знаю только один такой способ — привести к единому типу.

2) Замечание верное, в данном случае — это просто упрощение и допущение: тут всего 4 кнопки, создаются они вначале и им тут же всем задаются callback функции с передачей указателя this, т.е. nullptr в данном случае там в теории не должен появится никогда.

3) "Правильных" — это каких? Из тех что я знаю — использование callback класса с виртуальной функцией. Пример я приводил выше. Минус — в классе Application будет всего одна callback функция для всех объектов UiButton. Впрочем, она и сейчас одна, так что, возможно, переделаю именно на такой способ.
А еще мне не очень нравится в таком способе, что если понадобится еще какой-нибудь объект(UiScroll например), то уже нужно будет множественное наследование в классе Application. Ну и дополнительно еще где-то таблицы виртуальных функций будут место занимать. Зато снимутся все вопросы с типами и безопасностью.

Идея, заложенная в постулате, заключается в том, что нельзя в статических функциях-членах класса использовать нестатические переменные-члены и функции-члены без объекта данного класса.
Идея понятна, но то, как она преподнесена создало, как минимум у меня, инерцию мышления на преодоление которой я потратил пару вечеров.
Поэтому, Вы уж извините, никакой магии Вы не используете.
Может поэтому слово "магия" — было взято в кавычки? Все действительно происходит в рамках стандарта.
1) А как передать указатели на разные объекты в UiButton для последующей передачи в callback функцию? Я знаю только один такой способ — привести к единому типу.


Как вариант, использовать объект специального класса для вызова функции обратного вызова:
class ICallback {
public:
// Можно использовать NVI при желании/по необходимости.
    void operator()(// Параметры функции) = 0;
};


class ApplicationCallbackWrapper : public ICallbackWrapper {
public:
    ApplicationCallbackWrapper (Application* i_application)
    : application_ {i_application} {
        /* 
            При необходимости - проверка указателя на nullptr. Если считается,
            что вызов может быть NOOP, то можно разрешить использование нулевого
            указателя.
        */
    }

    void operator()(// Параметры функции) override {
        if (!application_) {
            /*
                Можно добавить обработку по умолчанию. Например, вывод 
                предупреждения в режиме отладки.
            */

            return;
        }

        // Необходимая обработка аргументов функции.

        /*
            Будем считать, что существует одна нестатическая функция-член, 
            которую необходимо вызывать в функции обратного вызова. В случае, 
            если требуется вызывать различные функции, можно написать несколько
            подобных классов-обёрток - по одному на каждую функцию. Если выбор
            функции должен осуществляться на основе аргументов функции - 
            необходимо добавить соответствующую обработку и
        */
        application_->Callback(// Аргументы для вызова основной функции);
    }


private:
    /*
        Можно использовать std::weak_ptr, что позволит узнавать об удалении
        объекта. Использование данного подхода требует, чтобы время жизни
        объекта (*application_) контроллировалось std::shared_ptr, что 
        ограничивает возможность создания объектов ApplicationCallbackWrapper
        из функций-членов класса Application. Данное ограничение можно обойти 
        через использование интрузивного подсчёта ссылок.
    */
    Application* application_;
};


/*
    При желании можно использовать владеющий (std::unique_ptr, 
    std::shared_ptr если требуется использовать один объект обратного вызова 
    из нескольких объектов) либо не владеющий (std::weak_ptr) указатель.
    В примене простой указатель для описания общей идеи. Если объект UiButton 
    владеет функтором обратного вызова, и используется обычный указатель, то 
    следует удалить объект при задании нового функтора обратного вызова и в 
    деструкторе.
*/
UiButton::SetCallback (ICallback* i_callback) {
    if (!i_callback) {
        // Необходимая обработка.
    }

    // callback_ - переменая-член класса UiButton для хранения функтора обратного вызова
    callback_ = i_callback;
}


UiButton::SomeFunction (...) {
    // ...

    if (callback_) {
        (*callback_)(// Аргументы);
    }

    // ...
}


int main (...) {
    // Используются простый указатели для краткости
    Application* application = new Application{};

    UiButton* button = new UiButton{};

    button->SetCallback (new ApplicationCallbackWrapper{application});
}


Альтернативно, можно использовать std::function:
class ApplicationCallbackWrapper {
    /*
        Такой же класс, как выше, но без наследования.
    */
};


class UiButton {
    using Callback = std::function <void()>;
    // ...
}


UiButton::SetCallback (UiButton::Callback i_callback) {
    if (!i_callback) {
        // Необходимая обработка.
    }

    // callback_ - переменая-член класса UiButton для хранения функтора обратного вызова
    callback_ = i_callback;
}


UiButton::SomeFunction (...) {
    // ...

    if (callback_) {
        callback (// Аргументы);
    }

    // ...
}


int main (...) {
    // Используются простый указатели для краткости
    Application* application = new Application{};

    UiButton* button = new UiButton{};

    button->SetCallback (ApplicationCallbackWrapper{application});

    /*
        Если можжно гарантировать, что время жизни application больше времени 
        жизни button, то можно использовать лямбда-функцию.
    */
    button->SetCallback ([application](// Параметры функции){
        // Использование application.
    });


    // Так тоже можно
    button->SetCallback (SomeFreeFunction);
}

Извиняюсь, в коде ошибки везде, где я закомментировал параметры функции. Должны быть комментарии в стиле /**/.
Слабенький генератор получается. Звуковых частот да и то…
Может лучше на DDS?
На той же AD9850

Старенький "ламповый" осциллограф! Когда-то у меня подобный Phillips был...

Побуду буквоедом, но PhiLLips — это отвёртка, а PhiLips — производитель техники.
settling time (...) составляет 3 ms

Микросекунд наверное, всё-таки.
И что это за странные всплески у вас на всех осциллограммах?

Я что-то не понял, зачем какие-то заморочки со статическими методами, какими-то кастами и прочим или java-style передача интерфейсов. Чем этот метод плох? Также можно и темплейт использовать. Пруф, можете проверить, что работает


Код
#include <iostream>
using namespace std;

class BaseFoo
{
public:
    virtual void bar()=0;
};

class Foo1: public BaseFoo
{
public:
    virtual void bar() override
    {
        cout << "Foo1::bar()\n";
    }
};

class Foo2: public BaseFoo
{
public:
    virtual void bar() override
    {
        cout << "Foo2::bar()\n";
    }
};

void func(BaseFoo* ptr, void (BaseFoo::*func)())
{
    (ptr->*func)();
}

int main()
{
    Foo1 foo1;
    Foo2 foo2;
    func(&foo1, &BaseFoo::bar);
    func(&foo2, &BaseFoo::bar);
    return 0;
}
UFO just landed and posted this here

Выше был пример, когда делали интерфейс нужного коллбека, передавали и вызывали. Как зачем? Это, собственно, та функция-член другого класса, которую мы хотим вызвать. Внутри метода func мы можем даже не знать, что это за метод. Мы можем вместо BaseFoo* использовать шаблон и даже не знать, что за класс. Такой подход применяется, к примеру, в std::thread. Я не стал писать пример с шаблоном, чтобы показать, что с полиморфизмом тоже работает.


Код
#include <iostream>
using namespace std;

class Foo
{
public:
    void bar()
    {
        cout << "Foo::bar()\n";
    }
};

template<class T>
void func(T* ptr, void (T::*func)())
{
    (ptr->*func)();
}

int main()
{
    Foo foo;
    func(&foo, &Foo::bar);
    return 0;
}
UFO just landed and posted this here

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


Также у нас может быть несколько функций-членов с подходящей сигнатурой, мы можем передать любую из них. Например, у нас есть класс Window, который содержит два объекта Button. Мы хотим, чтобы нажатие на них обрабатывались независимыми обработчиками.


Interface-way
class MyWindow: Window, OnClickListener
{
public:
    MyWindow()
    {
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    // Implement IClickListener (View - какой-нибудь базовый класс Button)
    virtual onClick(View& sender) override
    {
        // Кто вызвал метод? Надо проверить sender
    }
}

В Android+Java часто применяют анонимные классы.


Что делают в Android+Java
btn1.setOnClickListener(new OnClickListener(){ 
    @Override
    void onClick(View v) {doFoo();}
});

btn2.setOnClickListener(new OnClickListener(){ 
    @Override
    void onClick(View v) {doBar();}
});

С описываемым мною подходом можно поступить так:


Указатели на функцию
class MyWindow: Window
{
public:
    MyWindow()
    {
        btn1.setOnClickListener(this, &MyWindow::onBtn1Click);
        btn2.setOnClickListener(this, &MyWindow::onBtn2Click);
    }

    onBtn1Click(View& sender) {doFoo(); }
    onBtn2Click(View& sender) {doBar(); }
}
Ну первый пример, может быть, и не очень наглядный. Но если шаблон использовать, мы передать указатель на фунцию-член, в то время как принимающая функция ничего не знает о нашем классе/базовом классе.
Как это "не знает"? Очень даже знает! Потому что шаблонный класс есть только в исходниках, в скомпилированном коде будет много почти одинаковых классов с разными типами.
Да, можно в классе UiButton объявить указатель на callback шаблонным типом, и функция SetCallback() может принимать шаблонный тип. Но тогда для каждого типа callback'ов будет создаваться отдельный класс UiButton со свои кодом. Если callback один(как в этой статье) — проблем нет, будет все равно один экземпляр UiButton. А вот если программа посложнее, с множеством классов использующих UiButton, то объем исполняемого кода в бинарнике начнет расти очень даже значительно.

Вы посмотрели пример с кнопками? Он и без шаблонов делается, там может быть аргументом базовый класс окна или еще чего-нибудь. Но можно разные коллбеки в одном классе использовать, в отличие от способа, когда мы у переданного указаетля/ссылки на интерфейс вызываем определенный метод коллбека.


P.S. Собственно, я совершенно не претендую на то, что мой вариант самый лучший и/или единственно правильный. Просто это тот метод, который первый приходит мне в голову при упоминании ссылки на функцию-член.

Здравствуйте!

Помимо уже заданных вопросов у меня есть ещё один: при чём тут Java?

Также можно и темплейт использовать.

Для чего Вы предлагаете использовать шаблоны?
Помимо уже заданных вопросов у меня есть ещё один: при чём тут Java?

Ну, я часто встречал подход, когда класс реализует некий интерфейс слушателя события, в котором требуемый метод обратного вызова, и передают его куда надо. В Android такое постоянно.


Для чего Вы предлагаете использовать шаблоны?

Чтобы можно было передать указатель на функцию-член произвольного класса. Например, так реализовано в std::thread: https://stackoverflow.com/a/10673671

Ну, я часто встречал подход, когда класс реализует некий интерфейс слушателя события, в котором требуемый метод обратного вызова, и передают его куда надо. В Android такое постоянно.

Паттерн проектирования наблюдатель (если я конечно не ошибаюсь в данном случае) является универсальным и его применимость не зависит от языка программирования.

Распространённость варианта с интерфейсами в Java можно объяснить тем, что свободных функций нет (насколько я знаю), а сам язык позиционирует себя как объектно-ориентированный, что приводит к созданию классов на каждый чих.

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

Если я заврался на почве собственного невежества — покорнейше прошу поправить меня.

Чтобы можно было передать указатель на функцию-член произвольного класса. Например, так реализовано в std::thread: stackoverflow.com/a/10673671


Могу я попросить Вас привести короткий пример кода, а то никак не могу сообразить, где и как надо использовать шаблоны в данном случае. Самым гибким, на мой взгляд, является вариант с использованием std::function, который я описал выше.
Распространённость варианта с интерфейсами в Java можно объяснить тем, что свободных функций нет (насколько я знаю), а сам язык позиционирует себя как объектно-ориентированный, что приводит к созданию классов на каждый чих.

Именно поэтому я и назвал Java-style. Я не могу утверждать, что именно в Java это наиболее распространено, исключительно по скромному опыту Android разработки. В C# тоже нет свободных функций, но там есть делегаты (что-то вроде типизированного указателя на функцию с ссылки на объект) и события (фактически observer на уровня языка). А в Java нет и идут On***Listener на каждый чих.


Могу я попросить Вас привести короткий пример кода, а то никак не могу сообразить, где и как надо использовать шаблоны в данном случае.

Пример кода

Как-то так. Здесь DataLoader ничего не знает о классах, которые его используют, и чьи коллбеки он вызывает.


#include <iostream>
using namespace std;

struct SomeData{};

template<class T>
class  DataLoader
{
public:
    DataLoader(T* ptr, void (T::*callback)(SomeData))
        : _callback(callback), _ptr(ptr)
    {}

   void beginLoading()
    { 
        //...
        cout << "DataLoader::beginLoading()\n";
        SomeData some_data;
        (_ptr->*_callback)(some_data); 
    }
private:
    T* _ptr;
    void (T::*_callback)(SomeData);    
};

class Foo
{
public:
    void foo()
    {
        cout << "Foo::foo()\n";
        // do something        
        DataLoader<Foo> loader(this, &Foo::callback);
        loader.beginLoading();
    }

    void callback(SomeData data)
    {
        cout << "Foo::callback()\n";
    }
};

class Bar
{
public:
    void bar()
    {
        cout << "Bar::bar()\n";
        // do something        
        DataLoader<Bar> loader(this, &Bar::data_loaded);
        loader.beginLoading();
    }

    void data_loaded(SomeData data)
    {
        cout << "Bar::data_loaded()\n";
    }
};

int main()
{
    Foo foo;
    Bar bar;

    foo.foo();
    bar.bar();
    return 0;
}

Самым гибким, на мой взгляд, является вариант с использованием std::function, который я описал выше.

Соглашусь. Забыл о нем. А у std::function есть оверхед? Я их только с лямбдами использовал, а лямбды — объект создается.

Да, оверхед есть. Я не измерял, но думаю, что порядка 10-20 нс на современных процессорах (сужу по оверхеду boost::type_erasure). Создавать проблемы это будет только при очень высокой частоте вызовов и при высокой загрузке системы (load average 1.0 и выше). По крайней мере, так мне говорят голоса в моей голове.

По коду: про такой вариант не подумал, потому что Вами предложено обобщённое решение проблемы, а я пытался придумать, как применить шаблоны в рамках представленного в статье кода без его сильного изменения. Единственным недостатком Вашего варианта является, собственно, статический полиморфизм и сопутствующая невозможность использования прямолинейного использования динамического полиморфизма для объектов типа DataLoader. Решить это можно через стирание типов, но это приведёт к тому же, что происходит в std::function или boost::type_erasure, только самодельному.

Кстати, можно решить всю задачу целиком с использованием boost::type_erasure. Тогда уже можно передавать более сложные обработчики обратного вызова (если можно это так назвать), со сложным интерфейсом и т.п., без использования механизмов наследования напрямую. Наследование при этом магическим образом перенесётся в код, сгенерированный boost::type_erasure.
Sign up to leave a comment.

Articles