5

The codebase I’m working on was developed mostly pre-C++11. A lot of classes have a never-defined default constructor declared in the private section. I’m rather confident that in Modern C++, the Correct Way™ is to make them public and = delete them. I “upgraded” classes to this countless times by now and it never lead to problems.

My question is rather: Why was that done at all? This answer said that a default constructor is only ever provided if there’s no constructor given by the user (I guess that’s not including = default) and there’s no hint that it doesn’t apply to pre-C++11. Of course, there is a non-trivial constructor in all of my classes I’m talking about. So, is there a rationale for it that I am missing?

Quirin F. Schroll
  • 1,302
  • 1
  • 11
  • 25
  • 1
    If there is custom constructor then default constructor is not generated, so there is no reason to explicitly declare it private or delete it, except as a way to document that it is not available. – Marek R Apr 05 '22 at 13:04
  • 1
    Any observation about that team's rationale is a _guess_ and my guess is inertia. Perhaps the rules you cite were not understood well and like tabs vs. spaces, one copies the coding style that's already present. – Drew Dormann Apr 05 '22 at 13:09
  • 2
    Having a constructor `private:` and not implemented gives a different (in my opinion, less useful) error message than having a constructor `public:` and `= delete`. Having a constructor not be generated, and then attempted to be used also gives a decent error message. Six of one, half-dozen of the other. Boils down to what one considers best self-documenting code. For omitting the code, a reader may ponder "Did the dev intentionally omit that constructor, or was it an oversight?" – Eljay Apr 05 '22 at 13:10
  • @MarekR Your comment looks more like an answer: _The only justified reason can be documenting explicit intent._ Because in my case where this is done over and over again for seemingly no reason, it translates to: There is no proper reason; the people who wrote this erroneously thought they had to do this to be correct. – Quirin F. Schroll Apr 05 '22 at 13:13
  • 1
    The one case I can think of for making the default constructor private is if the class/struct only has static members and static member functions. Making the default constructor private enforces that the class cannot be instantiated, and must be used in a "static" sense. – PaulMcKenzie Apr 05 '22 at 13:20
  • 1
    @Bolpat: "*Because in my case where this is done over and over again for seemingly no reason, it translates to: There is no proper reason; the people who wrote this erroneously thought they had to do this to be correct.*" Why? Remembering the rules of when certain constructors are automatically included requires effort. Knowing what `= delete` means requires almost nothing. Being explicit is its own reward sometimes. – Nicol Bolas Apr 05 '22 at 13:20
  • @PaulMcKenzie I can only imagine one case why one would use a class with static members only instead of a namespace: A namespace cannot be passed as a template argument, but a class can. (This isn’t what my code base does.) – Quirin F. Schroll Apr 05 '22 at 13:25

2 Answers2

6

Any function can be = deleted. A default constructor is a function, so it can be deleted. There's no need to make a language carveout for that.

That some users choose to explicitly delete the default constructor (or the pre-C++ pseudo-equivalent of a private declaration with no definition) when it would not have been generated by the compiler is harmless. Indeed, it has some small benefits.

  1. If someone changes the class to remove its constructors, the class won't suddenly gain a default constructor.

  2. You don't have to remember what the rules are about when a default constructor is generated. For example:

    I guess that’s not including = default

    This proves my point, because you guessed wrong. Explicitly defaulted constructors do count as "user-provided", and thus they do suppress the creation of an implicit default constructor. Having to remember that is bothersome; it's clearer to just state it outright.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 3
    3. It makes it clear to future developers that the absence of a default constructor is intentional and not an omission. If there is no default constructor at all, it is easy to think someone forgot to implement one. When there is an inaccessible or deleted default constructor it becomes clear that this was intentional. – François Andrieux Apr 05 '22 at 13:33
0

As became clear in the comments under the question, having a constructor (or a member function) declared but never implemented is bad design that leads to bad error messages. The only justified reason to = delete something that would not have been generated anyways, is documenting explicit intent. In my case, none of the classes gives the impression that it should have a default construtor.

I’m still not sure what’s ideal in my case because that depends on who’s going to work on the code base in the furute. For now, I’ll continue to change private never-defiend to = delete, but leave it at that. It’s rather easy to find = delete constructors and replace them with nothing compared to restoring them after they’re removed.

Quirin F. Schroll
  • 1,302
  • 1
  • 11
  • 25
  • 2
    I would refine two points - having a member declared private and not implemented is a documented practice for C++03, emulating what `= delete` does in C++11. Also, a function declared as `= delete` that would not be generated does have purposes. Those functions are included in name lookup. – Drew Dormann Apr 05 '22 at 13:34
  • Kate Gregory has a nice presentation [What do we mean when we say nothing at all?](https://www.youtube.com/watch?v=-Hb-9TUyjoo) from ACCU 2019 where she presents a case about being explicit about these kinds of things, and that a problem (which cannot be fixed, that ship has sailed) with C++ is "all the defaults are wrong". – Eljay Apr 05 '22 at 13:37