Как самостоятельно задокументировать функцию обратного вызова, которая вызывается классом библиотеки шаблонов?

У меня есть функция User::func()(обратный вызов), которая будет вызываться классом шаблона (Library<T>).

На первом этапе разработки все знают, что func() служит только для этой единственной цели.
Несколько месяцев спустя большинство участников забывают, для чего предназначен func().
После серьезного рефакторинга некоторые программисты иногда удаляют func(). .

Сначала я вообще не думал, что это проблема.
Однако после того, как я несколько раз сталкивался с этой схемой, я решил, что мне нужны контрмеры.

Вопрос

Как это красиво задокументировать? (мило и лаконично и без дополнительных затрат на ЦП)

Пример

Вот упрощенный код:-
(Реальная проблема заключается в разбросе около 10+ библиотечных файлов, 20+ пользовательских файлов и 40+ функций.)

Библиотека.h

template<class T> class Library{
    public: T* node=nullptr;
    public: void utility(){
        node->func();  //#1
    }
};

User.h

class User{
    public: void func(){/** some code*/} //#1
    //... a lot of other functions  ...
    // some of them are also callback of other libraries
};

main.cpp

int main(){
    Library<User> li; .... ;  li.utility();
}

Мои плохие решения

1. Комментарий/документ

В качестве первого обходного пути я обычно добавляю такой комментарий: -

class User{ 
    /** This function is for "Library" callback */
    public: void func(){/** some code*/}
};

Но он довольно быстро загрязняется — мне приходится добавлять его в каждую «функцию» в каждом классе.

2. Переименуйте "func()"

В реальном случае я обычно добавляю префикс имени функции следующим образом: -

class User{ 
    public: void LIBRARY_func(){/** some code*/}
};

Это очень заметно, но имя функции теперь очень длинное.
(особенно когда Library-класс имеет более длинное имя класса)

3. Виртуальный класс с "func()=0"

Я рассматриваю возможность создания абстрактного класса в качестве интерфейса для обратного вызова.

class LibraryCallback{ 
    public: virtual void func()=0;
};
class User : public LibraryCallback{ 
    public: virtual void func(){/** some code*/}
};

Создается ощущение, что func() означает что-то совершенно внешнее. :)
Однако мне приходится жертвовать стоимостью виртуальных вызовов (v-table).
В критических для производительности случаях я не могу себе этого позволить.

4. Статическая функция

(идея от Daniel Jour в комментарии, спасибо!)

Почти 1 месяц спустя, вот как я использую: -

Библиотека.h

template<class T> class Library{
    public: T* node=nullptr;
    public: void utility(){
        T::func(node);  //#1
    }
};

User.h

class User{
    public: static void func(Callback*){/** some code*/} 
};

main.cpp

int main(){
    Library<User> li;
}

Это, вероятно, чище, но все еще не хватает самодокумента.


person javaLover    schedule 17.04.2017    source источник
comment
Одним из преимуществ решения с абстрактным базовым классом является то, что оно позволяет объявить LibraryCallback* node.   -  person Code-Apprentice    schedule 17.04.2017
comment
@ Code-Apprentice Спасибо. Это полезно. Однако в данном случае мне это не нужно. Я могу сделать это немного менее элегантно с T* t внутри class Library{}. Я согласен с тем, что виртуальный класс обеспечивает большое удобство при кодировании Library.   -  person javaLover    schedule 17.04.2017
comment
Одна вещь, которую я действительно не понимаю: удаление функции-члена должно привести к ошибке компилятора... разве этой документации недостаточно? Если функция-член библиотеки не создается из кода вашей библиотеки, вы можете добавить модульный тест, который делает это.   -  person Daniel Jour    schedule 17.04.2017
comment
@Daniel Jour Да, этого вполне достаточно. Тем не менее, было бы лучше, если бы код имел форму/форму, которая заметна сама по себе, например. при прокрутке в редакторе без какой-либо (вероятно долгой) компиляции.   -  person javaLover    schedule 17.04.2017
comment
Может быть несколько разных User классов (с разной func реализацией), верно? Если сразу не очевидно, что func имеет какое-либо применение и поэтому удаляется ... тогда, возможно, действительно было бы лучше иметь func не как функцию-член User, а, возможно, как свободную функцию или член некоторого вспомогательного класса?   -  person Daniel Jour    schedule 18.04.2017
comment
@Daniel Jour Да, это может иметь другую реализацию. Возможен вспомогательный класс. Его недостатком является то, что я должен передать вспомогательный класс/бесплатную функцию в качестве другого параметра шаблона Library<User,&freeFunc> или Library<User,HelperClass>, что немного грязнее. .... Но все же, я думаю, что ваша идея удобна и уникальна. ..... Если вы опубликуете это, я проголосую за это. Благодарить! XD   -  person javaLover    schedule 18.04.2017
comment
Нужен ли User::func() доступ к закрытым функциям User? В противном случае можно использовать класс свойств (отделяет реализацию от реализации User и делает очевидным, что это связь между User и Library).   -  person chtz    schedule 17.05.2017
comment
@chtz У меня разные ситуации. Однако чаще всего (70%) нет. Даже в этом сложном случае я думаю, что могу обойти ограничение, используя ключевое слово friend, и код все равно будет выглядеть разумно.   -  person javaLover    schedule 17.05.2017


Ответы (9)


func не является функцией User. Это особенность муфты User-Library<T>.

Помещать его в User, если он не имеет четкой семантики за пределами использования Library<T>, — плохая идея. Если у него есть четкая семантика, он должен сказать, что он делает, и его удаление должно быть явно плохой идеей.

Размещение его в Library<T> не работает, потому что его поведение является функцией T в Library<T>.

Ответ заключается в том, чтобы не размещать его ни в одном месте.

template<class T> struct tag_t{ using type=T; constexpr tag_t(){} };
template<class T> constexpr tag_t<T> tag{};

Сейчас в Library.h:

struct ForLibrary;
template<class T> class Library{
  public: T* node=nullptr;
  public: void utility(){
    func( tag<ForLibrary>, node ); // #1
  }
};

in User.h:

struct ForLibrary;
class User{ 
/** This function is for "Library" callback */
public:
  friend void func( tag_t<ForLibrary>, User* self ) {
    // code
  }
};

или просто поместите это в то же пространство имен, что и User, или в то же пространство имен, что и ForLibrary:

friend func( tag_t<ForLibrary>, User* self );

Прежде чем удалить func, вы отследите ForLibrary.

Он больше не является частью «общедоступного интерфейса» User, поэтому не загромождает его. Это либо друг (помощник), либо свободная функция в том же пространстве имен, что и User, или Library.

Вы можете реализовать его там, где вам нужен Library<User>, а не в User.h или Library.h, особенно если он просто использует общедоступные интерфейсы User.

Здесь используются такие методы, как «диспетчеризация тегов», «поиск в зависимости от аргумента», «функции друзей» и предпочтение бесплатных функций методам.

person Yakk - Adam Nevraumont    schedule 11.05.2017
comment
Эта техника (тэг) для меня авансовая. Могу ли я действительно передать tag_t<ForLibrary> как тип? Это не экземпляр объекта! (tag_t<ForLibrary>()) ...... Мне нужно время, чтобы найти и прочитать об этом, спасибо. - person javaLover; 12.05.2017
comment
@javaLover tag_t<ForLibrary> — это тип. tag<ForLibrary> является значением типа tag_t<ForLibrary>. Вместо этого вы можете использовать tag_t<ForLibrary>{} для создания значения. - person Yakk - Adam Nevraumont; 12.05.2017
comment
О, мой плохой. Я понимаю это сейчас. Кстати, это решение немного стоит от лишней передачи параметров, верно? Я знаю, что даже есть некоторые расходы, это будет очень мало, но я хочу знать. - person javaLover; 13.05.2017
comment
@javaLover В C++ передача параметров происходит в абстрактной машине; во время выполнения он может испариться, если передача включает в себя тривиально копируемые объекты (указатели, теги, целые числа и т. д.), структуры одного и того же или если встроенные нетривиальные перемещения/копии могут быть исключены компилятором. Я не вижу причин, по которым на любом уровне оптимизации без отладки здесь были бы какие-либо накладные расходы. - person Yakk - Adam Nevraumont; 13.05.2017
comment
Обратите внимание, что вызовы virtual нельзя оптимизировать. Но накладные расходы на передачу указателя и 1-байтового тега будет трудно обнаружить. - person Yakk - Adam Nevraumont; 13.05.2017
comment
Шаблон переменной не поддерживается в VC++, поэтому я не могу сделать его симпатичным и коротким. ( доказательство rextester.com/MEMEQ66100 ) Вы случайно не знаете какой-нибудь хороший обходной путь, кроме tag_t<ForLibrary>()? Было бы неплохо, если бы я мог опустить скобку (). - person javaLover; 14.05.2017
comment
Вы случайно не знаете, как исправить аналогичную проблему, возникающую в typedef? stackoverflow.com/questions/44109929 (новый вопрос) - person javaLover; 22.05.2017

Со стороны пользователя я бы использовал crtp для создания интерфейса обратного вызова и заставил пользователей использовать Это. Например:

template <typename T>
struct ICallbacks
{
    void foo() 
    {
        static_cast<T*>(this)->foo();
    }
};

Пользователи должны наследовать этот интерфейс и реализовать обратный вызов foo().

struct User : public ICallbacks<User>
{
    void foo() {std::cout << "User call back" << std::endl;}
};

Самое приятное в этом то, что если Library использует интерфейс ICallback и User забывает реализовать foo(), вы получите хорошее сообщение об ошибке компилятора.

Обратите внимание, что здесь нет виртуальной функции, поэтому здесь нет снижения производительности.

Со стороны библиотеки я бы вызывал эти обратные вызовы только через ее интерфейсы (в данном случае ICallback). Следуя OP при использовании указателей, я бы сделал что-то вроде этого:

template <typename T>
struct Library
{
    ICallbacks<T> *node = 0;

    void utility()
    {
       assert(node != nullptr);
       node->foo();
    }
};

Обратите внимание, что таким образом все автоматически документируется. Совершенно очевидно, что вы используете интерфейс обратного вызова, и node — это объект, у которого есть эти функции.

Ниже приведен полный рабочий пример:

#include <iostream>
#include <cassert>

template <typename T>
struct ICallbacks
{
    void foo() 
   {
       static_cast<T*>(this)->foo();
   }
};

struct User : public ICallbacks<User>
{
     void foo() {std::cout << "User call back" << std::endl;}
};

template <typename T>
struct Library
{
    ICallbacks<T> *node = 0;

    void utility()
    {
        assert(node != nullptr);
        node->foo();
    }
};

int main()
{
    User user;

    Library<User> l;
    l.node = &user;
    l.utility();
}
person Amadeus    schedule 11.05.2017
comment
Я бы получил уродливое предупреждение о том, что User попытаюсь скрыть foo() от родителя (ICallbacks). Есть ли способ избежать предупреждения (кроме использования #pragma disable warning)? - person javaLover; 12.05.2017
comment
@javaLover Я был слишком неосторожен с предупреждающими сообщениями, извините. Я отредактировал свой ответ, чтобы устранить эти предупреждения, пытаясь сохранить исходный контекст. - person Amadeus; 12.05.2017
comment
Если User не реализует foo(), это приведет к бесконечной рекурсии (во время выполнения)! - person chtz; 17.05.2017

Test.h

#ifndef TEST_H
#define TEST_H

// User Class Prototype Declarations
class User;

// Templated Wrapper Class To Contain Callback Functions
// User Will Inherit From This Using Their Own Class As This
// Class's Template Parameter
template <class T>
class Wrapper {
public:
    // Function Template For Callback Methods. 
    template<class U>
    auto Callback(...) {};
};

// Templated Library Class Defaulted To User With The Utility Function
// That Provides The Invoking Of The Call Back Method
template<class T = User>
class Library {
public:
    T* node = nullptr;
    void utility() {
        T::Callback(node);
    }
};

// User Class Inherited From Wrapper Class Using Itself As Wrapper's Template Parameter.
// Call Back Method In User Is A Static Method And Takes A class Wrapper* Declaration As 
// Its Parameter
class User : public Wrapper<User> {
public:
    static void Callback( class Wrapper* ) { std::cout << "Callback was called.\n";  }
};

#endif // TEST_H

main.cpp

#include "Test.h"

int main() {

    Library<User> l;
    l.utility();

    return 0;
}

Вывод

Callback was called.

Я смог скомпилировать, собрать и запустить это без ошибок в VS2017 CE в Windows 7 — 64-битный Intel Core 2 Quad Extreme.

Какие-нибудь мысли?

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



Изменить

Поигравшись с этой магией шаблона, нет такой вещи... Я закомментировал шаблон функции в классе Wrapper и обнаружил, что он не нужен. Затем я закомментировал class Wrapper*, который является списком аргументов для Callback() в User. Это дало мне ошибку компилятора, в которой говорилось, что User::Callback() не принимает 0 аргументов. Поэтому я оглянулся на Wrapper, так как User наследуется от него. На данный момент Wrapper — это пустой шаблон класса.

Это заставило меня взглянуть на Library. Библиотека имеет указатель на User в качестве общедоступного члена и функцию utility(), которая вызывает метод User's static Callback. Именно здесь вызывающий метод принимает указатель на объект User в качестве параметра. Так что это заставило меня попробовать это:

class User; // Prototype
class A{}; // Empty Class

template<class T = User>
class Library {
public: 
    T* node = nullptr;
    void utility() {
        T::Callback(node);
    }
};

class User : public A {
public:
    static void Callback( A* ) { std::cout << "Callback was called.\n"; }
};

И это правильно компилируется и строится как упрощенная версия. Однако; когда я думал об этом; версия шаблона лучше, потому что она выводится во время компиляции, а не во время выполнения. Итак, когда мы вернемся к использованию шаблонов, javaLover спросил меня, что означает class Wrapper* или находится в списке аргументов для метода Callback в классе User.

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

template<class T>
class Wrapper {   // Could Be Changed To A More Suitable Name Such As Shell or BaseShell
};

Когда мы смотрим на класс User:

class User : public Wrapper<User> {
public:
    static void Callback( class Wrapper* ) { // print statement }
};

Мы видим, что User — это нешаблонный класс, который наследуется от шаблонного класса, но использует себя в качестве аргумента шаблона. Он содержит общедоступный статический метод, и этот метод ничего не возвращает, но принимает один параметр; это также очевидно в классе Library, параметр шаблона которого является классом User. Когда метод Library's utility() вызывает метод User's Callback(), параметр, ожидаемый библиотекой, является указателем на объект User. Поэтому, когда мы возвращаемся к классу User, вместо того чтобы объявлять его как указатель User* непосредственно в его объявлении, я использую пустой шаблон класса, от которого он наследуется. Однако, если вы попытаетесь сделать это:

class User : public Wrapper<User> {
public:
    static void Callback( Wrapper* ) { // print statement }
};

Вы должны получить сообщение о том, что Wrapper* отсутствует список аргументов. Мы могли бы просто сделать Wrapper<User>* здесь, но это избыточно, поскольку мы уже видим, что User наследуется от Wrapper, который берет сам себя. Таким образом, мы можем исправить это и сделать его чище, просто добавив префикс Wrapper* с ключевым словом class, поскольку это шаблон класса. Отсюда и магия шаблонов... ну, здесь нет никакой магии... только встроенная компилятором и оптимизация.

person Francis Cugler    schedule 12.05.2017
comment
Изменить T в template<class T> auto Callback(...) {}; на T2? (на данный момент почти совместим ideone.com/7Ueuul) .... Кстати, что это Callback( class Wrapper* ) ? то есть, почему вы можете использовать class Wrapper* в качестве параметра функции? Это ново для меня. В чем его преимущество для этого решения (т.е. почему вы его используете)? - person javaLover; 12.05.2017
comment
@javaLover, если честно; Я не ожидал, что это сработает, но это сработало; должна быть магия шаблона. - person Francis Cugler; 12.05.2017
comment
@javaLover ... Я думаю, из-за того, что User наследуется от Wrapper, а Wrapper имеет User в качестве параметра шаблона. Метод обратного вызова, принадлежащий пользователю, который является статическим методом, имеет в качестве аргумента параметра объявление указателя класса, поскольку Wrapper является шаблоном класса. Было бы излишним использовать Wrapper<User> в качестве аргумента. Класс Wrapper не имеет экземпляра экземпляра класса, но имеет общедоступные шаблоны функций. - person Francis Cugler; 12.05.2017
comment
@javaLover ... Я как бы наткнулся на этот маленький трюк или хак с шаблонами; сначала я думал о том, чтобы попытаться использовать другой тип класса-оболочки шаблона, который содержит лямбда-выражения, но я не мог заставить его работать должным образом. - person Francis Cugler; 12.05.2017
comment
@javaLover Компилируется и запускается в Windows 7 с использованием MSVS 2017 CE. Не уверен, что он будет компилироваться на Clang, GCC или Mingw. - person Francis Cugler; 12.05.2017
comment
@javaLover Я закомментировал шаблон функции в классе-оболочке, и он по-прежнему компилируется, строится, запускается и по-прежнему вызывает User::Callback(). - person Francis Cugler; 12.05.2017
comment
@javaLover Я также закомментировал class Wrapper*, который является списком аргументов для метода обратного вызова в User, и когда я это сделал; он сказал, что User::Callback не принимает 0 аргументов. Итак, я проследил, чтобы посмотреть на Wrapper, и это был просто пустой класс тела. Затем я посмотрел на класс Library, и он имеет в качестве члена T*, который будет объектом User*. Это вызывает User для вызова метода обратного вызова с переданным ему User*. Поскольку User унаследован от Wrapper, он примет указатель объявления класса в качестве своего параметра. - person Francis Cugler; 12.05.2017
comment
1. Да, после редактирования ваш код также отлично работает в gcc. Я думаю, что ваш трюк похож на решение тега Yakk. Это может быть стандартный способ передачи типа в качестве параметра. (!?!) 2. Вы должны удалить ; в auto Callback(...) {};. Я получил ошибку компиляции в coliru. 3. Могу ли я удалить слово class в static void Callback( class Wrapper* )? Он все равно будет компилируемым. (coliru.stacked-crooked.com/a/1beafc04731d0aee) - person javaLover; 12.05.2017
comment
@javaLover; Я внес серьезные изменения в ответ, который вы должны проверить; класс Wrapper, который является шаблоном класса, должен быть пустой оболочкой, чтобы он работал. Базовый класс не может быть неполным типом, но может быть пустой оболочкой. - person Francis Cugler; 12.05.2017

Хотя я знаю, что не отвечаю на ваш конкретный вопрос (как задокументировать функцию, которую нельзя удалить), я бы решил вашу проблему (сохранив, казалось бы, неиспользуемую функцию обратного вызова в базе кода), создав экземпляр Library<User> и вызвав utility() в модульном тесте (или, может быть, его лучше назвать тестом API...). Это решение, вероятно, будет масштабироваться и для вашего реального примера, если вам не нужно проверять каждую возможную комбинацию библиотечных классов и функций обратного вызова.

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

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

person psyill    schedule 15.05.2017
comment
О, забудьте об этом, я только что прочитал комментарий Даниэля Джура к вашему вопросу. Но я поддержу его предложение ^_^ - person psyill; 15.05.2017
comment
По крайней мере, ваше решение заставляет меня еще больше задуматься о модульном тестировании, спасибо. - person javaLover; 16.05.2017

Вот решение с использованием класса Traits:

// Library.h:
template<class T> struct LibraryTraits; // must be implemented for every User-class

template<class T> class Library {
public:
    T* node=nullptr;
    void utility() {
        LibraryTraits<T>::func(node);
    }
};

// User.h:
class User { };

// must only be implemented if User is to be used by Library (and can be implemented somewhere else)
template<> struct LibraryTraits<User> {
    static void func(User* node) { std::cout << "LibraryTraits<User>::func(" << node << ")\n"; }
};

// main.cpp:

int main() {
    Library<User> li; li.utility();
}

Преимущества:

  • По названию очевидно, что LibraryTraits<User> требуется только для взаимодействия User с Library (и может быть удалено после удаления либо Library, либо User.
  • LibraryTraits может быть специализировано независимо от Library и User

Недостатки:

  • Нет легкого доступа к закрытым членам User (сделка LibraryTraits другом User лишила бы его независимости).
  • Если один и тот же func необходим для разных Library классов, необходимо реализовать несколько Trait классов (может быть решена реализациями по умолчанию, наследующими от других Trait классов).
person chtz    schedule 17.05.2017
comment
Удивительно, что даже при наличии большого количества решений вы все равно можете предложить еще одно уникальное со своим преимуществом. Благодарить. ... Кстати, я вовсе не считаю перечисленные вами недостатки недостатком. - person javaLover; 17.05.2017

Это сильно напоминает старый добрый дизайн, основанный на политике, за исключением того, что в вашем случае вы не наследовать класс Library от класса User. Хорошие имена — лучшие друзья любого API. Объедините это и известную фразу проектирования на основе политик (известность очень важна, потому что имена классов со словом Policy в нем сразу вызовут интерес у многих читателей кода), и, я полагаю, вы получите хорошо самодокументирующийся код.

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

  • Иметь четко выделяющиеся и согласованные имена среди нескольких User-подобных классов (например, SomePolicyOfSomething в порядке вышеупомянутого проектирования на основе политик), а также аргументы шаблона для Library (например, SomePolicy, или я бы назвал его TSomePolicy).

  • Наличие using объявления Callback в классе Library может привести к более четким и более ранним ошибкам (например, из IDE или современного clang, анализаторов синтаксиса visial studio для IDE).

Другим спорным вариантом может быть static_assert, если у вас C++>=11. Но в этом случае его нужно использовать в каждом User-подобном классе ((.

person Yuki    schedule 17.05.2017
comment
Я прочитал вашу вики-ссылку. Я никогда не знал, что я могу сделать это. Это интересно. Благодарить. - person javaLover; 18.05.2017

Не прямой ответ на ваш вопрос о том, как это документировать, но что-то, что следует учитывать:

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

#include <functional>
template<class Type, std::function<void(Type*)> callback>
class Library {
    // Some Stuff...
    Type* node = nullptr;
public:
    void utility() {
         callback(this->node);
    }
};

Можно сделать это еще более явным, чтобы другие разработчики знали, что это необходимо.

person TinfoilPancakes    schedule 17.05.2017
comment
К сожалению, проблемные разработчики — это те, кто вслепую кодирует User.h и [не имеет времени / скучает / не имеет стимула] изучать Library.h. Они все равно не получат никакой информации. - person javaLover; 17.05.2017
comment
Верно, но просто предложение на будущее :) Также, если они действительно удалят функцию в User, вы все равно можете передать ее, независимо от того, есть ли она в классе. - person TinfoilPancakes; 18.05.2017

абстрактный класс — лучший способ заставить функцию не удаляться. Поэтому я рекомендую реализовать базовый класс с чистой виртуальной функцией, чтобы производный должен был определить функцию. ИЛИ вторым решением было бы иметь указатели на функции, чтобы производительность сохранялась за счет избежания дополнительных накладных расходов на создание и вызов V-таблицы.

person rams time    schedule 17.04.2017
comment
Ваше первое решение такое же, как виртуальный класс с func () = 0, не так ли? Как ваше второе решение может уменьшить человеческий фактор? Как это способствует лучшей читабельности? - person javaLover; 17.04.2017
comment
У этого решения, похоже, есть потенциал (может быть представлено новое решение с уникальными плюсами и минусами). Тем не менее, некоторые фрагменты или более подробная информация все еще необходимы. - person javaLover; 18.04.2017

Если не очевидно, что func() требуется в User, то я бы сказал, что вы нарушаете единую ответственность принцип. Вместо этого создайте класс адаптера адаптера, членом которого будет User.

class UserCallback {
  public:
    void func();

  private:
    User m_user;
}

Таким образом, существование UserCallback подтверждает, что func() является внешним обратным вызовом, и отделяет потребность Library в обратном вызове от фактических обязанностей User.

person dlasalle    schedule 18.05.2017
comment
К сожалению, UserCallback.h до сих пор не получил никакой документации о том, что он связан с Library. - person javaLover; 18.05.2017