2

So I've seen many different methods used for this in different librarys, and I want to get a sense of which one may be preferred (or perhaps it's strictly personal preference/case by case). Just as an example, let's have a class that tries to open some file:

Method 1: Bools

class filepath {
  public:
    filepath(const std::string &s) : path_{s} {}
    bool is_dir() const { return is_dir_; }
    bool is_file() const { return is_file_; }
    bool is_link() const { return is_link_; }
    bool exists() const { return exists_; }

  private:
    const std::string path_;
    const bool is_dir_ = false;
    const bool is_file_ = false;
    const bool is_link_ = false;
    const bool exists_ = false;
};

if (f.exists()) { ... }

Potential benefits:

  • Similar to how a lot of higher level languages act.
  • No need for "magic" constants/global constants.

Potential downsides:

  • Not really contiguous, you may need several if () statements to figure out a specific state
  • Can flood your class with getters and bools that may or may not be packed efficiently

Method 2: Constant integers/Enums

constexpr int FINFO_NOEXIST = -1;
constexpr int FINFO_ISFILE = 0;
constexpr int FINFO_ISDIR = 1;
constexpr int FINFO_ISLINK = 2;

class filepath {
  public:
    filepath(const std::string &s) : path_{s} {}
    int info() const { return info_; }

  private:
    const std::string path_;
    const int info_ = FINFO_NOEXIST;
};

switch (f.info()) { ... }

Potential benefits:

  • Allows to handle every state in a single switch() statement
  • Everything gets packed into a single integer
  • Allows for abstraction, you can create is_xxx() functions that just check the integer and returns a bool.

Potential downsides:

  • The need for global/magic constants that need to be documented/tracked

Method 3: Bitflags

constexpr int FINFO_NOEXIST = 0x1;
constexpr int FINFO_ISFILE = 0x2;
constexpr int FINFO_ISDIR = 0x4;
constexpr int FINFO_ISLINK = 0x8;

class filepath {
  public:
    filepath(const std::string &s) : path_{s} {}
    int info() const { return info_; }

  private:
    const std::string path_;
    const int info_ = 0;
};

if (f.info() & FINFO_NOEXIST) { ... }

Similar to method 2, but probably preferred in cases where many flags may be set or unset.

4

I would at least consider yet another option: bit fields.

class filepath {
public:
    filepath(const std::string &s)
        : path_{ s }
        , is_dir_{ false }
        , is_file_{ false }
        , is_link_{ false }
        , exists_{ false }
    {}

    bool is_dir() const { return is_dir_; }
    bool is_file() const { return is_file_; }
    bool is_link() const { return is_link_; }
    bool exists() const { return exists_; }

private:
    const std::string path_;
    const bool is_dir_ : 1;
    const bool is_file_ : 1;
    const bool is_link_ : 1;
    const bool exists_ : 1;
};

This combines the cleanliness of your first choice with the dense storage and flexibility of the third, without creating a leaky abstraction that forces the client to be aware of how the data is stored.

  • Oh, this is certainly something! I didn't know about bit fields, but I can see their usefulness for old and new projects of mine. As far as I've seen, the two drawbacks I see are: that they can't be referenced through a pointer (at least not in C), and that their order is implementation-dependent (which is a hassle if you plan to serialize and deserialize between different platforms). – Jesus Alonso Abad May 2 '18 at 18:29
  • Nice, in fact I think I prefer this to my three examples. – ricco19 May 2 '18 at 18:35
  • No, this is bad. It suggests you can have a path that is multiple things (file and directory for instance). This is nonsense of course but it is what the code says. The types are no longer mutually exclusive, which is something that should be expressed by the code. Never favor a "smart" technical solution over a correct model, think harder for the sake of maintainability and the appreciation of your successors.. – Martin Maat May 3 '18 at 5:26
  • @MartinMaat: It's exactly like his third example in that respect. Some combinations (file and hidden, for example) make sense. Others don't. Most that don't should probably be combined into a single flag. For example, file vs. directory may be the two possible states of a single flag (but chances are that these are examples he didn't think through carefully, so their precise details may be irrelevant). – Jerry Coffin May 3 '18 at 5:29
1

With the exception of some limit cases, I think it's just a personal preference (or the style imposed by the company/project style guide). If I could choose any, I'd go for the bool approach as it's semantically clearer.

The limit cases I consider may be important (that come to my mind at this moment):

  • Memory limitations: You may need to pack them as much as possible, which would be using (and ideally combining) flags.
  • The number of flags: Note that if you need more than 32 flags (usual size of an integer) you're limited to either change the flags type to a long long or skip the flags approach.
  • More than one value at the same time can be used: if you can have both options FINFO_ISDIR and FINFO_ISLINK, either establish a priority between them or switch to the bool/bitmasks approaches. Unless the values of the enumeration/constants aren't completely independent (e.g., bitmasks), you can't use more than one at the same time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.