It is often recommended that class objects be initialized using direct constructors rather than assignment. [Meyers 01] Direct constructors avoids construction, copying, and destruction of a temporary copy of the object. To wit, object should be constructed this way:
Code Block | ||||
---|---|---|---|---|
| ||||
Widget w( /* constructor arguments */); |
rather than this way:
Code Block | ||||
---|---|---|---|---|
| ||||
Widget w = Widget( /* constructor arguments */); |
or this way (for classes that support this syntax)
Code Block | ||||
---|---|---|---|---|
| ||||
Widget w = /* constructor argument */; |
Besides being inefficient, this last syntax violates OOP32-CPP. Ensure that single-argument constructors are marked "explicit".
However, C++ parsers are often liable to misparsing constructor arguments. While compilers will often generate a compiler error upon such misparses, it is possible for such misparses to slip past a compiler and lurk in executable code, with unexpected results.
Non-Compliant Code Example
In this non-compliant example, the class Widget
has a default constructor.
Code Block | ||||
---|---|---|---|---|
| ||||
class Widget { public: explicit Widget() {cerr << "constructed" << endl;} }; int main() { Widget w(); return 0; } |
However, while a human may consider w
to be explicitly built with the default constructor, the compiler interprets w
to be a pointer to a function that takes no arguments, and returns a Widget
!
As a result, this program compiles and prints no output, because the default constructor is never actually invoked.
Compliant Solution
This situation is ameliorated by removing the parentheses after w
.
Code Block | ||||
---|---|---|---|---|
| ||||
class Widget { public: explicit Widget() {cerr << "constructed" << endl;} }; int main() { Widget w; return 0; } |
Running this program produces the single output constructed
.
Non-Compliant Code Example
Here is a more complex non-compliant example. The class Widget
maintains a single int
, and the class Gadget
maintains a single Widget
.
Code Block | ||||
---|---|---|---|---|
| ||||
class Widget { public: explicit Widget(int in) : i(in) {cerr << "widget constructed" << endl;} private: int i; }; class Gadget { public: explicit Gadget(Widget wid) : w(wid) {cerr << "gadget constructed" << endl;} private: Widget w; }; int main() { int i = 3; Gadget g(Widget(i)); cout << i << endl; return 0; } |
The declaration of g
is not parsed as a Gadget
with a 1-argument constructor. It is instead parsed as a pointer to a function that takes a single Widget
argument, called i
, and returns a Gadget
. For illustrative purposes, keep in mind that in a function declaration, parentheses around argument names are optional. So the following is a legitimate function declaration, and indicates how the compiler sees the above declaration:
Code Block | ||||
---|---|---|---|---|
| ||||
Gadget g(Widget i); |
As a result, this program compiles cleanly and prints only 3
as output, because no Gadget
or Widget
is constructed.
Compliant Solution
This situation is ameliorated by moving the Widget
construction outside Gadget
.
Code Block | ||||
---|---|---|---|---|
| ||||
int main() { int i = 3; Widget w(i); Gadget g(w); cout << i << endl; return 0; } |
Running this program produces the expected output:
widget constructed
gadget constructed
3
Risk Assessment
Not guarding implicit constructor parsing could lead to unexpected behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
OOP31-CPP | low | likely | low | P9 | L2 |
Bibliography
- [Meyers 01] Item 6: Be alert for C++'s most vexing parse.
OOP30-CPP. Do not invoke virtual functions from constructors or destructors 13. Object Oriented Programming (OOP) OOP33-CPP. Do not slice polymorphic objects