Proper typedef location in C++

Viewed 678

I would like to ask the proper location of typedef in C++.

Version1 : typedef outside class

typedef std::pair<std::string, int> StrIntPair;
typedef std::vector<StrIntPair> StrIntPairVec;

class MyData
{
public:
    MyData(){};
    ~MyData(){};
private:
    void addInfo(const StrIntPair &info)
    {
        infoVec.push_back(info);
    }
    StrIntPair info;
    StrIntPairVec infoVec;
};

Version2 : typedef inside class public

class MyData
{
public:
    MyData(){};
    ~MyData(){};
    typedef std::pair<std::string, int> StrIntPair;
    typedef std::vector<StrIntPair> StrIntPairVec;
private:
    void addInfo(const StrIntPair &info)
    {
        infoVec.push_back(info);
    }
    StrIntPair info;
    StrIntPairVec infoVec;
};

Version3 : typedef inside class private

class MyData
{
public:
    MyData(){};
    ~MyData(){};
private:
    typedef std::pair<std::string, int> StrIntPair;
    typedef std::vector<StrIntPair> StrIntPairVec;
    void addInfo(const StrIntPair &info)
    {
        infoVec.push_back(info);
    }
    StrIntPair info;
    StrIntPairVec infoVec;
};

Which version is the best practice?

2 Answers

This depends on where you use the type alias. I'd advice you to

  • Put them outside of the class if you use them across classes and/or functions and the meaning of the alias is not exclusively related to the class.
  • Define them as public class type aliases if client code outside of the class needs to access them (e.g. to initialize the object or to store an aliased return value of a member function) but the alias is related to the class. The alias then becomes a part of the class interface.
  • Define them as private class type aliases when you use them exclusively inside the class, e.g. some utility data structure that is annoying to type out all the time when passing it across member functions.

The compiler will only enforce scopes of aliases that are too narrow (e.g. you use a type alias defined in the private section of your class outside of that class) and won't complain if you choose an unnecessarily permissive scope (e.g. you publicly declare the alias, but use it only in the class implementation). Hence, strive to choose the narrowest scope possible, which is in line with hiding implementation details.

As a side note, you might want to consider declaring your type aliases with using StrIntPair = std::pair<std::string, int>;, see Item 9 in Effective Modern C++. This has no influence on the above, though.

The question is about logical namespace of those names. With abstract naming like StrIntPair, StrIntPairVec and MyData there are no answers. Answers come when the things have meaning.

Lets take exactly same data structures but name them NickAndId, Friends and Player.

Now the question if to put NickAndId inside Player is about if it is specific to player. Can other entities like NonPlayerCharacter or Creature also have nickname and id expressed as same pair? Possibly. Then it should be outside.

Same question should be asked about Friends. Likely the NonPlayerCharacter and Creature can have nickname and id but do not have friends? Then it makes sense to put the type inside of Player as Player::Friends.

Finally, the types that are made private are meant only for usage by implementation details. That should be used when the name makes perfect sense in algorithms used inside of class but availability of those outside is unneeded or even worse, confusing. For example NonPlayerCharacter can react with some replicas to some states whose values are also internal to that NPC. Keeping that in sorted vector Reactions makes perfect sense inside of class. Access to ReplicaInState and Reactions from outside can be confusing.

Related