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.
(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?
Conclusion: All other things being equal, your code will run faster if you use initialization lists rather than assignment.
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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With