Как передать обратный вызов функции члену класса?

#c #templates #function #callback #pass-by-reference

#c #шаблоны #функция #Обратный вызов #передача по ссылке

Вопрос:

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

Мне нужно передать функцию сравнения конструктору класса, предопределенную функцию сравнения для целых чисел и тому подобное, а затем предоставить клиенту предоставлять любые функции сравнения, которые они могут захотеть. Но как мне это сделать?

 template<typename Type>
int cmpFn(Type one, Type two)
{
    if (one < two) return -1;
    if (one > two) return 1;
    return 0;
}

template <typename Type>
class Queue
{
    public:
        Queue()
        {
            Type *list = new Type[size];
            // What do I do now?
            // How to define this constructor?
            // It must pass a comparison function
            // to a private sort method in this class.
        }
    private:
        void sortFunc(Type list, int(fn)(Type one, Type  two)=cmpFn);
};
  

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

Это личное упражнение, я не записан ни на какой курс и у меня нет доступа к каким-либо преподавателям. Я уже некоторое время гуглю этот вопрос, но не смог найти правильный ответ… Думаю, я задал не тот вопрос мистеру Google.

P.S. Клиент может захотеть предоставить функции сравнения для любого вида данных, например:

 struct individual
{
    string name;
    int age;
    double height;
};
  

Я предполагаю, что конструктор должен быть таким:

 Queue(int (*fn)(Type, Type) = cmpFn);
  

Но как мне определить / реализовать это? Эту функцию обратного вызова будет использовать не сам объект очереди, а его метод: sort();

Комментарии:

1. Это не ответ, но в духе C я бы спроектировал это по-другому; а именно, ожидайте, что T в комплекте будет operator< или специализация std::less<T> , и используйте это для сортировки. Возможно, даже предоставить итераторы, хотя это может не помочь для структуры, подобной очереди.

Ответ №1:

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

 #include <cstddef>
#include <string>
#include <iostream>

template<typename Type>
int cmpFn(Type one, Type two)
{
    if (one < two) return -1;
    if (one > two) return 1;
    return 0;
}

template <typename Type>
class Queue
{
    public:
        // This is the typedef for your callback type
        typedef int (*callback)(Type one, Type two);

        Queue(Type *list, size_t size, callback func = cmpFn)
        {
            sortFunc(list, size, func); // works too
        }

    private:
        void sortFunc(Type *list, size_t size, callback func) {
            for (size_t i=0; i<size; i  ) {
                for (size_t j=0; j<size; j  ) {
                    if (i == j) continue;

                    int val = (*func)(list[i], list[j]);
                    switch (val) {
                        case 1:
                            std::cout << list[i] << " is greater than " << list[j] << "n";
                            break;
                        case -1:
                            std::cout << list[j] << " is greater than " << list[i] << "n";
                            break;
                        case 0:
                            std::cout << list[i] << " and " << list[j] << " are equaln";
                            break;
                    }
                }
            }
        }

};

int stringCmp(std::string one, std::string two) {
    if (one.size() < two.size()) return -1;
    if (one.size() > two.size()) return 1;
    return 0;
}

int main() {
    // compare ints, use generic comparison function (cmpFn)
    int myInts[2] = {1, 2};
    Queue<int> qi(myInts, 2);

    // compare strings, use our own comparison function (stringCmp)
    std::string myStrings[2] = {"foo", "bar"};
    Queue<std::string> qs(myStrings, 2, stringCmp);

    return 0;
}
  

Компиляция и выполнение приведенной выше программы должны дать вам этот результат:

 2 is greater than 1
2 is greater than 1
foo and bar are equal
bar and foo are equal
  

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

  • Queue Конструктор принимает list массив, его размер и функцию обратного вызова.
  • Если функция обратного вызова не предусмотрена, используется универсальная функция ( cmpFn ).
  • Затем он вызывает sortFunc который перебирает все элементы в list массиве и сравнивает их с помощью функции обратного вызова.

В приведенном выше коде приведен пример с int и std::string .

Комментарии:

1. Спасибо! Это почти то, что мне нужно. Теперь мне нужно только выяснить, как сохранить ссылку на функцию обратного вызова в объекте класса. И наоборот, я не хочу, чтобы sortFunc автоматически вызывался конструктором, вместо этого клиент будет вызывать его при необходимости способом myqueue.sortFunc(); или myqueue->sortFunc(); Клиент указывает, какую функцию сравнения для сортировки использовать при построении объекта: Queue<int> myqueue(cmpFuncStrings);

2. @user971191: Для этого вам придется сделать sortFunc общедоступным (это было закрыто, поэтому я предполагал, что вы этого не сделаете). Если вы хотите сохранить обратный вызов, callback определяется как тип, поэтому просто объявите элемент этого типа и определите его в конструкторе.

3. У меня все это было все время перед глазами … спасибо, что указали на это! 🙂

Ответ №2:

Может быть, вот так:

 void sortFunc(Type list, int(*fn)(Type one, Type  two) = cmpFn<Type>);
  

Вероятно, лучше всего передавать аргументы по const-ссылке, а не по копии. Кто знает, будут ли они вообще копируемыми?

Комментарии:

1. Спасибо за ответ, но как мне заставить этот класс хранить функцию сравнения, предоставленную клиентом? Клиент предоставит способ сравнения элементов, содержащихся в списке. Я сам определяю только стандартную функцию сравнения. Я действительно запутался в этом.

2. А, я понял. Вы могли бы сделать функцию сравнения параметром шаблона вашего класса, а затем добавить соответствующий аргумент конструктора. Взгляните на то, как set это реализовано (или, по крайней мере, ознакомьтесь с его открытым интерфейсом).

3. @user971191: И если вы не хотите, чтобы это было частью аргументов шаблона, вам придется выполнить некоторое удаление типа.