Tania Marinova
Tania Marinova

Reputation: 1908

Macros for creating named constructors

I am trying to play with c++ and macros for the first time. SO basically in a lecture I know where a coded value is used to distinguish different modes of an object (game_type in this case), then these objects must be created via constructors with meaningful named constructors. SO I created createSinglePlayerGame() and named constructors. Then I tried to optimize this code using macros. SO In the Game class I define the function- like macro consructor A ## operator which runs parameter replacement on the two identifiers and then concatenates the result ("token pasting).

Can you look at my code and suggest a better way of doing this, also do you see any hygiene problem that my macros may be used incorrectly,

 class Game
    {
    public:
    #define CONSTRUCTOR(name, a) static Game create_##name() { return Game(a);}

            CONSTRUCTOR(Single, 0)
            CONSTRUCTOR(Multiple, 2)


        // named constructors
        static Game createSinglePlayerGame() { return Game(0); }
        static Game createMultiPlayerGame() { return Game(1); }

    protected:
        Game(int game_type);
    };
    int main()
    {
        Game myGame = Game::createSinglePlayerGame();
        Game second = Game::create_Single();

    }

Upvotes: 0

Views: 955

Answers (3)

hoo2
hoo2

Reputation: 525

If you want an alternative to macros you can also use templates. Templates is something that compiler "understand" and optimize.

Their syntax may be confusing at start but if you play with them you can get used to it...

// Tag types for template dispatching
struct SinglePlayer {};
struct MultiPlayer {};

// Default single player
template <typename G>
class Game {
    public:
        Game () { /* create default single player game */ }
};

// Specialization for multi player
template <>
class Game <MultiPlayer> {
    public:
        Game () { /* create multiplayer player game */}
};

int main (void) {
    Game g1 = Game<SinglePlayer>();
    Game g2 = Game<MultiPlayer>();
}

This approach split the class definition into 2. One for single player and one for multi player. If they share a lot of common staff and you believe that this should not be the case, you may consider to avoid the class template and use std::enable_if<> in the constructor's level.

For example:

#include <type_traits>

// Tag types for template dispatching
struct SinglePlayer{};
struct MultiPlayer{};

struct Game {
    public:
    template <
        typename G,
        std::enable_if_t<std::is_same<G, SinglePlayer>::value, int> = 0
    >
    Game(G) { /* create a single player game */ }
    template <
        typename G,
        std::enable_if_t<std::is_same<G, MultiPlayer>::value, int> = 0
    >
    Game(G) { /* create a multi player game */ }
};

int main (void) {
    Game g1 = Game(SinglePlayer{});
    Game g2 = Game(MultiPlayer{});
}

Notice that there is no class template any more, only a SFINAE dispatch for the constructors. The disadvantage is the dummy pass of a SinglePlayer{} or MultiPlayer{} object to the constructor.

Upvotes: 0

G. Sliepen
G. Sliepen

Reputation: 7984

As an alternative to John Zwincks solution, you can also use inheritance:

class Game {
protected:
    Game(int game_type);
};

class SinglePlayerGame: public Game {
public:
    SinglePlayerGame(): Game(0) {}
};

class MultiPlayerGame: public Game {
public:
    MultiPlayerGame(): Game(1) {}
};

int main() {
    SinglePlayerGame myGame;
    ...
}

Although I would only use this method if there were more differences in the interface between single- and multiplayer games than just the constructor.

Upvotes: 1

John Zwinck
John Zwinck

Reputation: 249642

A more conventional way would be:

enum class GameType {
    SinglePlayer,
    MultiPlayer,
};

class Game
{
public:
    explicit Game(GameType type);
};

int main()
{
    Game myGame(GameType::SinglePlayer);
}

This is simpler and will be less surprising for other C++ developers to read. It's also less error-prone: even your example code confuses 1 and 2 for multiplayer mode, and since you use raw integers to store it, there's no complaint from the compiler. Using enum class it will be much harder to make such mistakes.

Upvotes: 5

Related Questions