How to understand the typedef in this declaration
Asked Answered
B

5

5

Recently, I read the book Effective C++ and there is a declaration about typedef in Item 35 which confuses me.

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;
};

So my first question is: Why does the author use a forward declaration here? Is there any specific reason?

And my second question is: How do I understand the typedef declaration, and how do I use it? I just know something like typedef int MyInt;

Badlands answered 12/3, 2017 at 8:54 Comment(0)
H
9

So my first question is that why the author use forward declaration here?

So that the compiler knows that GameCharacter is a valid name when the int defaultHealthCalc(const GameCharacter& gc); line is encountered.

And my second question is how to understand the typedef declaration and how to use it?

Ideally, you do not use it anymore.

Starting with C++11, using is a superior alternative because it's more readable; unlike typedef'ed function pointers, it clearly separates the name from that which the name describes. Compare this:

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

With this:

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

In the typedef version, the name HealthCalcFunc is surrounded on both sides by that which the name describes. This hurts readability.


But the code can still be improved, because C++11 also introduced std::function as an alternative to and/or an abstraction layer above function pointers.

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

This is so readable it hardly has to be explained at all. HealthCalcFunc is a function which returns an int and takes a const GameCharacter&.

The defaultHealthCalc function fits into this definition. Here's a full example:

#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;
};

The great thing about std::function is that you are not restricted to free-standing functions. You can pass every function-like thing. Here are some examples:

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));
}

See also Should I use std::function or a function pointer in C++?.

Hirsh answered 12/3, 2017 at 10:53 Comment(5)
"Because the code can still be improved": mentioning std::function here is appropriate, but it is a fully different alternative, and for a simple example such as this one, it might be mis-leading to mention improvements when switching from a light weight function pointer type to the entirely different and, in comparison, "heavy" creature of std::function. Both have their place (mostly, imo, std::function should be preferred, but when working with older APIs ...), but are very different concepts, and whether to use one over the other should be weighted against the context.Mayolamayon
@dfri: I think std::function is much simpler to write, read and apply than a function pointer, so I consider it the standard solution, whereas function pointers are special-purpose beasts.Hirsh
Yes I agree, note my ninja-edit. Still, when mentioning std::function as an always-improvement, it might be valuable to point out that there are situations where a simple function pointer is to prefer (or even the only alternative). Anyway, good answer, just a minor byline by me above!Mayolamayon
@dfri: I've added a link to a question which examines the advantages and disadvantages of function pointers compared to std::function.Hirsh
@ChristianHackl This one is the best! Thank you so much!Badlands
H
14

So my first question is that why the author use forward declaration here?

Because the declaration of the function defaultHealthCalc uses const GameCharacter& as the type of the parameter, then GameCharacter needs to be declared in advance.

And my second question is how to understand the typedef declaration

It declares a type name HealthCalcFunc, which is a type of pointer to function, which takes const GameCharacter& as parameter and returns int.

and how to use it?

Just as the code sample showed,

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 
Handbreadth answered 12/3, 2017 at 9:0 Comment(0)
B
11

The forward declaration is needed because of the forward declaration of defaultHealthCalc, which uses GameCharacter. Without forward declaration, the compiler would not know that later, there will be a type named GameCharacter, and so you need to forward declare it, or move the definition before the function declaration.

That typedef means to name the type int(*)(const GameCharacter&) HealthCalcFunc. That's a function pointer to a int(const GameCharacter&). For that reason, typedefs are pretty unreadable, it is advised to use a using declaration instead:

using HealthCalcFunc = int(*)(const GameCharacter&);
Bueschel answered 12/3, 2017 at 8:59 Comment(0)
I
9
typedef int (*HealthCalcFunc)(const GameCharacter&);

Declares a typedef named HealthCalcFunc for a function pointer type, where the function signature returns a int and takes a parameter of const GameCharacter&.

The forward declaration is needed because that class is used as parameter type in the typedef.

Insouciance answered 12/3, 2017 at 8:58 Comment(0)
H
9

So my first question is that why the author use forward declaration here?

So that the compiler knows that GameCharacter is a valid name when the int defaultHealthCalc(const GameCharacter& gc); line is encountered.

And my second question is how to understand the typedef declaration and how to use it?

Ideally, you do not use it anymore.

Starting with C++11, using is a superior alternative because it's more readable; unlike typedef'ed function pointers, it clearly separates the name from that which the name describes. Compare this:

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

With this:

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

In the typedef version, the name HealthCalcFunc is surrounded on both sides by that which the name describes. This hurts readability.


But the code can still be improved, because C++11 also introduced std::function as an alternative to and/or an abstraction layer above function pointers.

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

This is so readable it hardly has to be explained at all. HealthCalcFunc is a function which returns an int and takes a const GameCharacter&.

The defaultHealthCalc function fits into this definition. Here's a full example:

#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;
};

The great thing about std::function is that you are not restricted to free-standing functions. You can pass every function-like thing. Here are some examples:

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));
}

See also Should I use std::function or a function pointer in C++?.

Hirsh answered 12/3, 2017 at 10:53 Comment(5)
"Because the code can still be improved": mentioning std::function here is appropriate, but it is a fully different alternative, and for a simple example such as this one, it might be mis-leading to mention improvements when switching from a light weight function pointer type to the entirely different and, in comparison, "heavy" creature of std::function. Both have their place (mostly, imo, std::function should be preferred, but when working with older APIs ...), but are very different concepts, and whether to use one over the other should be weighted against the context.Mayolamayon
@dfri: I think std::function is much simpler to write, read and apply than a function pointer, so I consider it the standard solution, whereas function pointers are special-purpose beasts.Hirsh
Yes I agree, note my ninja-edit. Still, when mentioning std::function as an always-improvement, it might be valuable to point out that there are situations where a simple function pointer is to prefer (or even the only alternative). Anyway, good answer, just a minor byline by me above!Mayolamayon
@dfri: I've added a link to a question which examines the advantages and disadvantages of function pointers compared to std::function.Hirsh
@ChristianHackl This one is the best! Thank you so much!Badlands
F
2

The forward declaration of GameCharacter is not strictly needed; the function declaration could have read:

int defaultHealthCalc(const class GameCharacter& gc);

which has the same effect as the original code. But it is considered more readable to have the forward declaration on its own line.

Fatherland answered 13/3, 2017 at 0:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.