Callback функции в C++

189
20 декабря 2021, 05:40

Я хотел бы удостовериться, что пишу все правильно и адекватно. Подскажите пожалуйста какие ошибки присутствуют в коде и желательно советы, как сделать правильно.

#include <iostream>
using namespace std;

class Point {
private:
    int xPos, yPos;
public:
    Point(int x, int y) : xPos(x), yPos(y) { }
    Point() : Point(0,0) { }
    int x() { return xPos; }
    int y() { return yPos; }
    void setX(int x) { this->xPos = x; }
    void setY(int y) { this->yPos = y; }
};
class Square {
private:
    void(*callBackFunc)(Point point);
    Point posLeftUp;
public:
    Square(){}
    Square(Point pos) : posLeftUp(pos) {}
    //Имитация движения квадрата по окну
    void Move(char key) {
        if (key == 'A')
            posLeftUp.setX(posLeftUp.x() - 2);
        else if (key == 'D')
            posLeftUp.setX(posLeftUp.x() + 2);
        else if (key == 'W')
            posLeftUp.setY(posLeftUp.y() + 2);
        else if (key == 'S')
            posLeftUp.setY(posLeftUp.y() - 2);
        callBackFunc(posLeftUp);
    }
    void setCallbackFunc(void(*fn)(Point point)) {
        callBackFunc = fn;
    }
};
//Имитация окна
class MainWindow {
private:
    static void getPosition(Point point);
    Square square;
public:
    MainWindow() {
        Point pos(10, 10);
        square = Square(pos);
        square.setCallbackFunc(getPosition);
        square.Move('A');
        square.Move('D');
        square.Move('W');
        square.Move('S');
    }
};
void MainWindow::getPosition(Point point){
        cout << point.x() << " : " << point.y() << endl;
}
int main() {
    MainWindow test;
    return 0;
}
Answer 1

Вот некоторые моменты, о которых я могу высказаться.

В определении класса я бы сначала указал public, так как это является интерфейсом класса.

class Point {
public:
    Point(int x, int y) : xPos(x), yPos(y) { }
    Point() : Point(0,0) { }
    int x() { return xPos; }
    int y() { return yPos; }
    void setX(int x) { xPos = x; }
    void setY(int y) { yPos = y; }
private:
    int xPos;
    int yPos;
};

Не объединяйте декларации в одну строку:

    // плохо:
    // int xPos, yPos; 
    // хорошо:
    int xPos;
    int yPos;

В setX и setY не используйте this:

void setX(int x) { xPos = x; }
void setY(int y) { yPos = y; }

Используйте switch в Move(). Он сюда очень хорошо вписывается.

void Move(char key) {
    switch(key)
    {
    case 'A': posLeftUp.setX(posLeftUp.x() - 2); break;
    case 'D': posLeftUp.setX(posLeftUp.x() + 2); break;
    case 'W': posLeftUp.setY(posLeftUp.y() + 2); break;
    case 'S': posLeftUp.setY(posLeftUp.y() - 2); break;
    }
    callBackFunc(posLeftUp);
}

Используйте const переменные, где они не должны меняться. Компилятор поможет вам избежать глупых ошибок:

void Move(const char key) {

В main() можно убрать return 0;. Когда main() достигнет конца, и return при этом отсутствует, то return 0; будет подразумеваться автоматически.

Под "Отделять объявления от определений в классе", о чём упомянул @Andrey, имеется ввиду создание отдельных файлов для объявления класса и для определения его методов. Взять за пример ваш class Square:

Создадим два файла: square.hpp и square.cpp.

square.hpp:

#ifndef SQUARE_HPP
#define SQUARE_HPP
#include <functional>
#include "Point.hpp"   // Файл, содержащий объявление класса Point
class Square {
public:
    Square();
    Square(const Point pos);
    void Move(const char key);
    void setCallbackFunc(std::function<void(const Point p)>);
private:
    std::function<void(const Point p)> callBackFunc;
    Point posLeftUp;
};
#endif //SQUARE_HPP

В этом файле мы объявили класс Square, то есть перечислили его методы и члены. Этого достаточно для того, чтобы знать как использовать этот класс где-либо ещё в другом месте.

Вот эта конструкция называется Header guard ("страж заголовка" - так это будет по-русски?)

#ifndef SQUARE_HPP
#define SQUARE_HPP
// ...
#endif //SQUARE_HPP

Она предотвращает многократное вложение этого hpp файла в один и тот же cpp файл при компиляции.

Детали же того, как реализованы данные методы поместим во второй файл

square.cpp:

#include "square.hpp"
Square::Square() : callBackFunc(nullptr)
{}
Square::Square(const Point pos) :
    callBackFunc(nullptr),
    posLeftUp(pos)
{}
void Square::Move(const char key)
{
    switch(key)
    {
    case 'A': posLeftUp.setX(posLeftUp.x() - 2); break;
    case 'D': posLeftUp.setX(posLeftUp.x() + 2); break;
    case 'W': posLeftUp.setY(posLeftUp.y() + 2); break;
    case 'S': posLeftUp.setY(posLeftUp.y() - 2); break;
    }
    if(callBackFunc)
    {
        callBackFunc(posLeftUp);
    }
}
void Square::setCallbackFunc(std::function<void(const Point p)> fn)
{
    callBackFunc = fn;
}

Таким образом мы отделяем интерфейс класса от реализации его методов.

Кстати, класс Point на самом деле мог бы быть просто структурой, так как его приватные члены xPos и yPos полностью доступны для чтения и записи через методы, которые вы определили. setX() и setY() не выполняют никакой дополнительной работы, то есть, здесь нет никакой выгоды в том, чтобы прятать x и y как приватные члены:

struct Point{
    Point() : x(0), y(0) {};
    Point(const int x, const int y) : x(x), y(y) {};
    int x;
    int y;
};

Другое дело, если бы мы обязали setX() и setY() выполнять проверку переданных значений, то прятать x и y как приватные было бы оправданно:

void Point::setX(const int val)
{
    constexpr int min_value = -100;
    constexpr int max_value = 100;
    if (val < min_value || val > max_value)
    {
        // Ошибка. Переданное значение неприемлемо.
        throw std::runtime_error("setX(): значение вне диапазона");
    }
    x = val;
}

По поводу calback. Используйте инструменты из заголовка <functional>:

std::function<> - класс оболочка, который может держать в себе функцию.

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

#include <functional>
#include <iostream>
using namespace std;

class Point {
public:
    Point(int x, int y) : xPos(x), yPos(y) { }
    Point() : Point(0,0) { }
    int x() { return xPos; }
    int y() { return yPos; }
    void setX(int x) { xPos = x; }
    void setY(int y) { yPos = y; }
private:
    int xPos;
    int yPos;
};
class Square {
public:
    Square(){}
    Square(Point pos) : posLeftUp(pos) {}
    //Имитация движения квадрата по окну
    void Move(const char key) {
        switch(key)
        {
        case 'A': posLeftUp.setX(posLeftUp.x() - 2); break;
        case 'D': posLeftUp.setX(posLeftUp.x() + 2); break;
        case 'W': posLeftUp.setY(posLeftUp.y() + 2); break;
        case 'S': posLeftUp.setY(posLeftUp.y() - 2); break;
        }
        if(callBackFunc)
        {
            callBackFunc(posLeftUp);
        }
    }
    void setCallbackFunc(std::function<void(const Point p)> fn) {
        callBackFunc = fn;
    }
private:
    std::function<void(const Point p)> callBackFunc = nullptr;
    Point posLeftUp;
};
//Имитация окна
class MainWindow {
private:
    static void getPosition(Point point);
    Square square;
public:
    MainWindow() {
        using namespace std::placeholders;
        Point pos(10, 10);
        square = Square(pos);
        square.setCallbackFunc(std::bind(&MainWindow::getPosition, _1));
        square.Move('A');
        square.Move('D');
        square.Move('W');
        square.Move('S');
    }
};
void MainWindow::getPosition(Point point){
        cout << point.x() << " : " << point.y() << endl;
}
int main() {
    MainWindow test;
}
READ ALSO
Ввожу русские буквы, выводит непонятные символы C++

Ввожу русские буквы, выводит непонятные символы C++

Написал программку для расставления букв в алфавитном порядке в любом словеПоставил setlocale(0, "rus")

111
C++: перевод простой дроби в десятичную

C++: перевод простой дроби в десятичную

Подскажите пожалуйста с алгоритмом:

125
Почему код успешно компилируется?

Почему код успешно компилируется?

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

72