Always-Valid C++ Types

Types don’t always contain what their name implies

Many C++ user-defined types may at time carry information that does not correspond to what their names say. E.g.: a Name object may be empty, which is not a valid name. All code that manipulates these types must handle such invalid cases, or trust that they have been verified elsewhere, which is a common source of bugs.

As an example, let’s consider a type that should contain a MIME string, e.g.: “audio/mp3”. A naive implementation may start like this:

class MIME {
public:
    MIME(const string& mime) : _mime(mime) {}
    bool is_valid() const { /* proper tests */ }
private:
    std::string _mime;
};

Notice that there is no validity checks in the constructor, so any code that manipulates MIME objects will need to call is_valid() before trusting that these objects contain valid MIME data. But sometimes in the spirit of optimization it is tempting to write:

// Only call with a valid mime string!
bool is_audio(const MIME& mime) {
    // This should catch any bad calls, right?
    assert(mime.is_valid());
    ...
}

And this may well pass all your testing, because you have already tested MIME::is_valid() elsewhere so you will think you don’t need to check for invalid MIME strings in is_audio() again… Until you start getting crash reports from the field, when your users handle data of doubtful origin.

Ensure types are valid by construction

One solution would be to add validity checks in the constructor, and throw an exception when a bad string is provided. However this seems quite a strong action to take, as strings that are not MIME types are quite common in the real world, and it would force the user to handle the MIME validation before trying to create a MIME object. Also some projects prefer not to use exceptions at all (like Firefox).

My preferred solution is to hide the too-trusting constructor, and to instead provide a factory function that will verify the given string and will only create a MIME object when that string is valid. Any object generated from this function can now be trusted to always contain valid data.

class MIME {
public:
    static std::optional create(const std::string& mime) {
        if (`mime` is a valid MIME string) {
            return { MIME(mime); }
        }
        return {};
    }
private:
    MIME(const string& mime) : _mime(mime) {}
    std::string _mime;
};

C++17’s optional is of great help here as it forces the caller to do the right thing: The return value must obviously be checked for a valid MIME object, and that object may only be accessed when actually present. Afterwards, that MIME object may be copied, or just passed to other functions as a direct reference; and by contract there is no need for further validity tests.

If you cannot use C++17 yet (and don’t have access to a similar helper type), you could instead return a unique_ptr, at the extra cost of a heap allocation — which may make sense anyway for heavy objects.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s