Как понять typedef в этом объявлении

Недавно я прочитал книгу Effective C++, и в статье 35 есть объявление о typedef, которое меня смущает.

class GameCharacter; // Question1: Why use forward declaration?

int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter{

    public:
        typedef int (*HealthCalcFunc)(const GameCharacter&); // Question 2: What does this mean?

        explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
            : healthFunc(hcf)
        {}

        int healthValue() const
        {return healthFunc(*this); }

    private:
        HealthCalcFunc healthFunc;
};

Итак, мой первый вопрос: почему автор использует здесь предварительное объявление? Есть какая-то конкретная причина?

И мой второй вопрос: как мне понять объявление typedef и как его использовать? Я просто знаю что-то вроде typedef int MyInt;


person Lixun Bai    schedule 12.03.2017    source источник


Ответы (5)


Итак, мой первый вопрос: почему автор использует здесь предварительную декларацию?

Чтобы компилятор знал, что GameCharacter является допустимым именем, когда встречается строка int defaultHealthCalc(const GameCharacter& gc);.

И мой второй вопрос: как понимать объявление typedef и как его использовать?

В идеале вы не его больше не используете.

Начиная с C++11, using является лучшей альтернативой, поскольку она более удобочитаема; в отличие от указателей на функции typedef, он четко отделяет имя от того, что это имя описывает. Сравните это:

typedef int (*HealthCalcFunc)(const GameCharacter&);

С этим:

using HealthCalcFunc = int (*)(const GameCharacter&);

В версии typedef имя HealthCalcFunc окружено с обеих сторон тем, что описывает имя. Это вредит читабельности.


Но код все еще можно улучшить, потому что C++11 также представил std::function в качестве альтернативы и/или уровня абстракции над указателями функций.

using HealthCalcFunc = std::function<int(const GameCharacter&)>;

Это настолько читабельно, что даже объяснять не нужно. HealthCalcFunc — это функция, которая возвращает int и принимает const GameCharacter&.

Функция defaultHealthCalc подходит под это определение. Вот полный пример:

#include <functional>

class GameCharacter;

int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter {
public:
      using HealthCalcFunc = std::function<int(const GameCharacter&)>; 

      explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
       : healthFunc(hcf)
      {}
      int healthValue() const
      {return healthFunc(*this); }
private:
       HealthCalcFunc healthFunc;
};

Самое замечательное в std::function то, что вы не ограничены отдельными функциями. Вы можете передать любую функциональную вещь. Вот некоторые примеры:

struct Functor
{
    int f(const GameCharacter& gc);
};

int main()
{
    // normal function:
    GameCharacter character1(defaultHealthCalc);

    // lambda:
    GameCharacter character2([](auto const& gc) { return 123; });

    // using std::bind:
    Functor functor;
    using namespace std::placeholders;
    GameCharacter character3(std::bind(&Functor::f, functor, _1));
}

См. также Должен ли я использовать std::function или функцию указатель в C++?.

person Christian Hackl    schedule 12.03.2017
comment
Поскольку код все еще можно улучшить: упоминание std::function здесь уместно, но это совершенно другая альтернатива, и для простого примера, такого как этот, это может быть неправильно - приводит к упоминанию улучшений при переключении с легкого типа указателя функции на совершенно другое и, по сравнению с ним, тяжелое существо std::function. Оба имеют свое место (в основном, imo, std::function следует предпочесть, но при работе со старыми API...), но это очень разные концепции, и следует ли использовать один над другим, следует взвешивать в зависимости от контекста. - person dfrib; 12.03.2017
comment
@dfri: я думаю, что std::function намного проще писать, читать и применять, чем указатель на функцию, поэтому я считаю его стандартным решением, тогда как указатели на функции - это звери специального назначения. - person Christian Hackl; 12.03.2017
comment
Да, согласен, обратите внимание на мой ниндзя-редактор. Тем не менее, упоминая std::function как постоянное улучшение, может быть полезно указать, что есть ситуации, когда простой указатель на функцию является предпочтительным (или даже единственной альтернативой). В любом случае, хороший ответ, просто небольшая подпись от меня выше! - person dfrib; 12.03.2017
comment
@dfri: я добавил ссылку на вопрос, в котором рассматриваются преимущества и недостатки указателей на функции по сравнению с std::function. - person Christian Hackl; 12.03.2017
comment
@ChristianHackl Это лучший! Большое спасибо! - person Lixun Bai; 14.03.2017

Итак, мой первый вопрос: почему автор использует здесь предварительную декларацию?

Поскольку объявление функции defaultHealthCalc использует const GameCharacter& в качестве типа параметра, то GameCharacter необходимо объявить заранее.

И мой второй вопрос: как понять объявление typedef

Он объявляет имя типа HealthCalcFunc, которое является типом указателя на функцию, которая принимает const GameCharacter& в качестве параметра и возвращает int.

и как его использовать?

Как показано в примере кода,

explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) // declare a parameter with type HealthCalcFunc; 
                                                               // the default argument is function pointer to defaultHealthCalc
: healthFunc(hcf)                                              // initialize healthFunc with hcf
{}

int healthValue() const
{return healthFunc(*this); }                                   // invoke the function pointed by healthFunc

HealthCalcFunc healthFunc;                                     // declare a member with type HealthCalcFunc 
person songyuanyao    schedule 12.03.2017

Упреждающее объявление необходимо из-за упреждающего объявления defaultHealthCalc, в котором используется GameCharacter. Без предварительного объявления компилятор не знал бы, что позже будет тип с именем GameCharacter, поэтому вам нужно объявить его вперед или переместить определение перед объявлением функции.

Это typedef означает назвать тип int(*)(const GameCharacter&) HealthCalcFunc. Это указатель функции на int(const GameCharacter&). По этой причине typedef довольно нечитаемы, вместо этого рекомендуется использовать объявление using:

using HealthCalcFunc = int(*)(const GameCharacter&);
person Rakete1111    schedule 12.03.2017

typedef int (*HealthCalcFunc)(const GameCharacter&);

Объявляет typedef с именем HealthCalcFunc для типа указателя на функцию, где сигнатура функции возвращает int и принимает параметр const GameCharacter&.

Предварительное объявление необходимо, потому что этот класс используется в качестве типа параметра в typedef.

person πάντα ῥεῖ    schedule 12.03.2017

Предварительное объявление GameCharacter строго не требуется; объявление функции могло бы выглядеть так:

int defaultHealthCalc(const class GameCharacter& gc);

который имеет тот же эффект, что и исходный код. Но считается более удобочитаемым иметь предварительное объявление в отдельной строке.

person M.M    schedule 13.03.2017