11

So we were doing some peer review, and this minor disagreement rose up,

Should the default constructor be defined even if it does nothing, or should we let the compiler define it?

So far, none of the sides could come up with any major advantages or disadvantages. What are the pros and cons of each style and which one is considered "cleaner"?

user1708860
  • 1,683
  • 13
  • 32
  • @nosid does it matter? but c++11 for the matter – user1708860 Mar 04 '14 at 22:03
  • Is there a single advantage to defining it? Two disadvantages are: more code to maintain and get confused about, and you can actually get it wrong, at least n C++03. – juanchopanza Mar 04 '14 at 22:03
  • So, slight issue. This seems like a pretty opinion-based question, and that's not considered a good sort of question for Stack Overflow. – Chris Forrence Mar 04 '14 at 22:05
  • 3
    @user1708860 In C++11 you can use "=default" with constructors. – Étienne Mar 04 '14 at 22:06
  • Also, you have to be clear about a default constructor that "does nothing". What does that mean? – juanchopanza Mar 04 '14 at 22:09
  • Without the `= default`, you could, for example, change your aggregate type into a non-aggregate type. – chris Mar 04 '14 at 22:10
  • The problem with this question is that it's vague and opinion-based. It's not the same as wondering whether or not user-defined empty constructors should be used in certain situations. –  Mar 04 '14 at 22:13
  • 1
    Let's put it this way: code with a needless user-provided default constructor would be unlikely to pass any code review I was on :-) – juanchopanza Mar 04 '14 at 22:14
  • 1
    Asking for specific pros and cons makes this a valid, non-opinion based question. – Adrian McCarthy Mar 04 '14 at 22:14

2 Answers2

16

This is likely to be closed as "primarily opinion-based," but I can give you some objective points to consider:

  • If you don't define the default constructor, and someone later adds a constructor with parameters and forgets to also add the parameterless constructor, the default constructor will go away and that could break existing code. Explicitly defining it ensures that even if someone adds an overloaded constructor later, the parameterless one is still there.

  • If the constructor is declared in the header and defined out-of-line (in a .cc/.cpp file), then the implementation can later be modified with dependent code only needing to be re-linked. Declaring a constructor after the fact necessarily affects the header, requiring recompilation of dependent code.

    • An empty out-of-line constructor will still need to be called, incurring a small run-time cost, whereas with an implicitly provided default constructor the compiler can see that nothing needs to be done and avoid the call.

  • Defining it explicitly requires more typing and results in more lines of code. There is a small but nonzero cost associated with this (time taken to type it in, and time taken for readers of the code to read through it).

  • Defining it explicitly disqualifies the class from being an aggregate class, unless you use =default in C++11.

Yes, these points are contradictory. I think you will find that the prevailing opinion is not to define it explicitly, but as far as the language is concerned there is no correct or incorrect way. (Unless you need your type to be an aggregate.)

Community
  • 1
  • 1
TypeIA
  • 16,916
  • 1
  • 38
  • 52
  • You can't say there is no performance/behaviour difference if you don't know for sure that the user provided constructor does the same as the compiler generated one. – juanchopanza Mar 04 '14 at 22:07
  • @juanchopanza OP is specifically talking about the case where the parameterless constructor does nothing; so no, there is no difference. – TypeIA Mar 04 '14 at 22:10
  • But it isn't clear what they mean by "do nothing". Besides that, having a constructor makes the class a non-aggregate. So it does change things. – juanchopanza Mar 04 '14 at 22:11
  • Just curious, how are aggregate classes treated differently from non-aggregate? – John Yost Mar 04 '14 at 22:14
  • @juanchopanza, Does `= default`'ed constructor count as a user declared constructor when it comes to whether a class is an aggregate? – eerorika Mar 04 '14 at 22:14
  • 2
    @user2079303 User declared != user-defined. You can have explicitly defaulted constructors and it'll still be an aggregate. –  Mar 04 '14 at 22:14
  • @juanchopanza I think it's clear that "do nothing" means "empty function body." Good point about the aggregate though; I will add that point to the answer. – TypeIA Mar 04 '14 at 22:15
  • But what about the initalization list? If the class has built-ins, the user provided constructor would have to value initialize them in the constructor to ensure that value initialization of the class propagates in C++03. The compiler-generated one takes care of that. – juanchopanza Mar 04 '14 at 22:18
  • @juanchopanza Sorry, I don't follow that point; in what situation would you be *required* to provide an initializer list where the compiler could do it automatically? If a built-in requires a constructor argument the compiler can't generate a default constructor anyway. – TypeIA Mar 04 '14 at 22:19
  • `struct Foo{ int a; Foo() {} };` vs `struct Bar{ int a; Bar() : a() {}};`. Value -initialization of a `Foo` leaves `Foo::a` uninitialized in C++03, but the same is not true of `Bar` or an equivalent class with no user-provided constructors. – juanchopanza Mar 04 '14 at 22:24
  • 1
    @juanchopanza I see, but I would consider that *not* a "does-nothing" constructor. It does something by initializing `Bar::a`. Maybe we can just stipulate that for the scope of this answer the constructor has an empty body and no initializer list? – TypeIA Mar 04 '14 at 22:26
  • "There is absolutely no performance difference whatsoever between defining it explicitly or letting the compiler do it." - not necessarily true... an explicitly defined constructor may be deliberately kept out of line, while an implicit one would often be inline; out-of-line is likely slower. Separate from performance, an out-of-line implementation may be later updated in its object/library and client code need only relink (static or dynamic) rather than recompile. – Tony Delroy Mar 04 '14 at 22:28
  • And in that case (assuming a built-in data member) it does something *different* to the compiler generated one. – juanchopanza Mar 04 '14 at 22:28
  • 1
    @user1708860: because 1) that's not a very complete answer by itself, 2) I don't want to plagarise the other points made in this answer to make a complete answer, 3) this answer should be updated to not mislead readers. – Tony Delroy Mar 04 '14 at 22:34
  • @TonyD I have removed the statement about performance altogether and added your point about relinking vs. recompiling. – TypeIA Mar 04 '14 at 22:39
  • @dvnrrs: as you've written it, it's not quite correct as the critical issue is whether the definition is in the header (where it's likely inline to avoid O.D.R issues) or out-of-line in an implementation (.cpp/.cc/whatever) file. I'll make a small edit to clarify - feel free to undo it if you're uncomfortable with it.... Cheers. – Tony Delroy Mar 05 '14 at 13:57
2

Without any user c'tors

You should only implement your own default constructor it it does anything else then the one the compiler would generate.

If you want to give the reader a hint, you can replace the implementation with = default in C++11.

struct MyObject {
    // only members that the compiler initializes fine 
    std::vector<int> data_;
    MyObject() = default;
};

or, if you don't wan to be that verbose (or before C++11):

struct MyObject {
    // only members that the compiler initializes fine 
    std::vector<int> data_;
};

With other user c'tors

The compiler will not generate a default c'tor if you provide any other c'tor. in this case you should only provide a default c'tor if it makes sense, semantically -- not because it's "nice" to have one :-)

Pros and Cons

Explicitly providing an unnecessary default c'tor

  • (-) it is bad to have more code then necessary
  • (o) except when the gained clarity outweighs the longer source code (= default)
  • (-) a compiler-generated default c'tor will be close to optimal
  • (-) if you start providing an unnecessary member ("unnecessary", because it would be generated), you later end up defining all of the auto generated ones, i.e. default-c'tor, destructor, copy, move, assign and move-assign. You really don't want to end up there.
  • (-) will you know if you should mark the default c'tor with noexcept? The compiler often does. Don't waist your brain powers where the compiler can help you.

I really can not see any clear (+), but that's just me.

towi
  • 21,587
  • 28
  • 106
  • 187