The class X2 in the example of C.42 describes a File class (wrapping a FILE*).
If the default constructor is intentionally missing from the example then it suggests a design where an X2 is always a valid file (similar to not_null<>). When I need a variable which is either a valid file or not I can explicitly use a construct like std::optional<X2>. This design is supported by the existence of not_null<>.
On the other hand, the default constructor may be missing from the example by chance. A default constructor would initialise the FILE* f member to nullptr, effectively implementing the optional semantics within X2. Then, an X2 object can be an either actual file or invalid. This design is supported by C.43 Ensure that a class has a default constructor
Which design is the preferred one? This should be clarified in the Guidelines.
From the whole set of guidelines, C.43 was the most surprising to me.
Ensuring that a class has a default constructor implies that you can always have a valid object from that class "out of nothing". As mentioned above, this guideline can encourage developers to create classes with ambiguous states.
In a FileHandler class for example, one would have to ask the object if it has a valid file or not.
In my daily development (and code review), I tend to avoid classes with default constructors that would leave the object in such state. That often causes paranoic code: one has to check every object to make sure it's doesn't contain dummy data.
I make extensive use of std::optional for that to avoid such states. If you have to check it, this is explicitly registered. Once it's checked, you take it out of the optional, and propagate it downstream in the interfaces.
I say this was the most surprising to me because many of the other guidelines have exceptions, or aren't so strict ("Ensure that a class..."), while this seems to be a hard rule one to a topic that I go almost the opposite way on daily work.
I agree it's overstated, and the examples are not persuasive. Arrays and std::vector only require a default constructor if you default construct the elements, this works fine:
struct X { X(int) { } };
X a[2] = { 0, 0 };
std::vector<X> v(10, X(0));
(The vast majority of std::vector operations don't need default constructors.)
So all the examples show is that if you try to use a default constructor you need a default constructor.
There is no "natural" default date ...
So why does a default constructor make sense? And what does a vector of 1000 "default dates" mean?
I believe the point of the date example was as the response to the "not every class has a natural default". It just picks one value of the set of valid values for the class and designates it to be the result of the default ctor. An array might have been a better demo of how that makes simple tasks simple.
(Personally, I was against default ctors, but warmed up to them due to moved-from states and the Stepanov's book)
Per our editors' discussion, should we restrict C.43 to value types and things that are movable/copyable? Or should we specify an exception for things like lock guards and vectors? We should also highlight that the Date example here is a value-like type, and these are default constructable, and also add a non-value-like example, maybe a base class, that should be default constructible.
C.42 simply presents the aspects of the class relevant to the rule. Note the // ... That's where whatever else you want should be.
emphasize value types; give example of types with no suitable default value.
Most helpful comment
From the whole set of guidelines, C.43 was the most surprising to me.
Ensuring that a class has a default constructor implies that you can always have a valid object from that class "out of nothing". As mentioned above, this guideline can encourage developers to create classes with ambiguous states.
In a FileHandler class for example, one would have to ask the object if it has a valid file or not.
In my daily development (and code review), I tend to avoid classes with default constructors that would leave the object in such state. That often causes paranoic code: one has to check every object to make sure it's doesn't contain dummy data.
I make extensive use of std::optional for that to avoid such states. If you have to check it, this is explicitly registered. Once it's checked, you take it out of the optional, and propagate it downstream in the interfaces.
I say this was the most surprising to me because many of the other guidelines have exceptions, or aren't so strict ("Ensure that a class..."), while this seems to be a hard rule one to a topic that I go almost the opposite way on daily work.