Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constraint checking in constructor initialization lists

This situation is related to How to make a constraint on the parameters of the constructor, but it's slightly different.

You want to initialize a non-default-constructible member but need to check for constraints before constructing it.

Example:

(Please note that this is really just an example. Whether one should use unsigned integers instead in this specific situation is discussable, but the question really is about the general case where you want to check in constructors)

You have the following class:

class Buffer {
public:
    Buffer() = delete;
    Buffer(int size) noexcept;
};
....


class RenderTarget {
public:
    ....
private:
    int width_, height_;
    Buffer surface_;
};

The constructor has to check the integer arguments for validness:

RenderTarget::RenderTarget(int width, int height) :
    width_(width), height_(height),
    surface_(width_*height)
{
    if (width_<0 || height_<0)
        throw std::logic_error("Crizzle id boom shackalack");
}

Note how Buffer does not have a default constructor, and the real constructor is noexcept, i.e. there is no way to catch an error.

When the integer arguments are negative, one has a hosed surface_ already. It would be nicer to do the constraint checking before using the constrained value. Is it possible?

like image 772
Sebastian Mach Avatar asked May 23 '14 13:05

Sebastian Mach


People also ask

Should my constructors use initialization lists or assignment?

Conclusion: All other things being equal, your code will run faster if you use initialization lists rather than assignment.

What is initialization list in constructor?

Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.

How constructor solve the problem of initialization?

The constructors should be used to initialize member variables of the class because member variables cannot be declared or defined in a single statement. Therefore, constructors are used in initializing data members of a class when an object is created.

Does initializer list run before constructor?

As already answered, initialization lists get completely executed before entering the constructor block. So it is completely safe to use (initialized) members in the constructor body.


1 Answers

Named Constructor

You can use a so called Named Constructor (see also https://isocpp.org/wiki/faq/ctors#named-ctor-idiom), and make the constructor private:

class RenderTarget {
private:
    RenderTarget (int w, int h) :
        width_(w), height_(h), buffer_(w*h) 
    {
        // NOTE: Error checking completely removed.
    }

public:
    static RenderTarget create(int width, int height) {
        // Constraint Checking
        if (width<0 || height<0)
            throw std::logic_error("Crizzle id boom shackalack");

        return RenderTarget(width, height);
    }

Named Constructors are interesting in case you have multiple constructors that may be ambiguous to use, e.g. Temperature <-- Celsius | Fahrenheit | Kelvin or Distance <-- Meter | Yard | Cubit | Kilometers | ....

Otherwise, (personal opinion) they impose an unexpected abstraction and also distraction and should be avoided.

Ternary Operator and throw

C++ allows in [expr.cond] the use of throw-expressions in one or both operands to the ternary operator (?:-operator):

RenderTarget(int w, int h) :
    width_(w<0 ? throw std::logic_error("Crizzle id boom shackalack") : w),
    height_(h<0 ? throw std::logic_error("Crizzle id boom shackalack") : h),
    surface_(w*h)
{}

If you do not store the arguments, you can also use ?: inside an expression, of course:

RenderTarget(int w, int h) :
    surface_(
       (w<0 ? throw std::logic_error("Crizzle id boom shackalack") : w)
     * (h<0 ? throw std::logic_error("Crizzle id boom shackalack") : h)
    )
{}

Or you combine the precondition-checking into a single operand:

RenderTarget(int w, int h) :
    surface_(
       (w<0||h<0) ? throw std::logic_error("Crizzle id boom shackalack") :
       w * h
    )
{}

Using the ?:-operator with a throw-expression inline can be very nice for basic constraint checking and avoids having to fall back to using a default constructor (if any), and then doing "real initialization" within the constructor body.

This can become a bit unwieldy for more complex scenarios.

Static Private Member

The best of both worlds can be used, of course:

private:
    static bool check_preconditions(int width, int height) {
        if (width<0 || height<0)
            return false;
        return true;
    }
public:
    RenderTarget(int w, int h) :
        surface_(
           check_preconditions(w,h) ?
           w*h :
           throw std::logic_error("Crizzle id boom shackalack")
        )
    {}

... or you write static functions for any member you need to precondition-check:

private:
    static Buffer create_surface(int width, int height) {
        if (width<0 || height<0)
            throw std::logic_error("Crizzle id boom shackalack")
        return Buffer(width*height);
    }

public:
    RenderTarget(int w, int h) :
      surface_(create_surface(w, h))
    {}

This is nice because you have the complete C++-machinery at hand for constraint checking, and for example can easily add logging. It scales well, but is a bit less handy for simple scenarios.

like image 182
Sebastian Mach Avatar answered Sep 19 '22 10:09

Sebastian Mach