Why Explicitly Delete the Constructor Instead of Making It Private

Why explicitly delete the constructor instead of making it private?

How about:

//deleted constructor
class Foo
{
public:
Foo() = delete;
public:
static void foo();
};

void Foo::foo()
{
Foo f; //illegal
}

versus

//private constructor
class Foo
{
private:
Foo() {}
public:
static void foo();
};

void Foo::foo()
{
Foo f; //legal
}

They're basically different things. private tells you that only members of the class can call that method or access that variable (or friends of course). In this case, it's legal for a static method of that class (or any other member) to call a private constructor of a class. This doesn't hold for deleted constructors.

Sample here.

Purpose of explicitly deleting the default constructor

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.

Should the deleted default constructor be in Public or Private?

  • You don't need to use the deleted ctor.
class A
{
public:
A(const int &val) : assign(val){}
private:
int assign;
};
int main()
{
A a;
return 0;
}

gives error: no matching function for call to ‘A::A()’

and

class A
{
public:
A() = delete;
A(const int &val) : assign(val){}
private:
int assign;
};
int main()
{
A a;
return 0;
}

gives error: use of deleted function ‘A::A()’

  • Concerning the use of delete, it could be useful with inheritance.
class A
{
public:
A() : assign(0){}
A(int val) : assign(val){}
private:
int assign;
};

class B : public A
{
public:
B() = delete;
B(int val) : A(val){};
};

class C : public A
{

};

class D : public A
{
public:
D() = delete;
};

int main()
{
A a1; //works
A a2(5); //works

//B b1; -- does not work : error: use of deleted function ‘B::B()’
B b2(3); //works

C c1; //works, creates a default ctor
//C c2(7); -- does not work : no matching function for call to ‘C::C(int)’

//D d1; -- does not work :error: use of deleted function ‘D::D()’
//D d2(2); -- does not work: error: no matching function for call to ‘D::D(int)’
return 0;
}

Note that D can't be instantiated. Quite useless though :)

What's the point of deleting default class constructor?

Consider the following class:

struct Foo {
int i;
};

This class is an aggregate, and you can create an instance with all three of these definitions:

int main() {
Foo f1; // i uninitialized
Foo f2{}; // i initialized to zero
Foo f3{42}; // i initialized to 42
}

Now, let's say that you don't like uninitialized values and the undefined behaviour they could produce. You can delete the default constructor of Foo:

struct Foo {
Foo() = delete;
int i;
};

Foo is still an aggregate, but only the latter two definitions are valid -- the first one is now a compile-time error.

Which is the difference between declaring a constructor private and =delete?

Making it private is the "old" way of doing it. The constructor still exists, but it is private, and can only be invoked from within another class member function.

= delete deletes the constructor. It is not generated by the compiler, and it simply will not exist.

So most likely, = delete is what you want. (although with the caveat that not all compilers support this syntax yet, so if portability is a concern...)

Deletion of copy-ctor & copy-assignment - public, private or protected?

what is the right place to do it - in the public, private or protected section of the class?

I would put them in the public section.

This is because deleting a constructor or an assignment operator is orthogonal to making them private / protected; and when these aren't deleted, they are public by default. Putting the deletions in one of those two sections seems to me like hinting "If I hadn't deleted them, I would have made them private/protected" - which is not a message you want to convey in your case.

Note, though, that the compiler doesn't care which section you put the deletion in.

Difference in constructors with X() = delete; and private X();

Example 1 was the way to do it before we had = delete which came out in C++11. Now that we have = delete this will completely get rid of the constructor. With making the constructor private you could still use that constructor in a member function where if you try to default an object in a member function with = delete it will be a compiler error.

#include <iostream>

class Foo
{
Foo();
public:
static void SomeFunc() { Foo f; }
};

class Bar
{
public:
Bar() = delete;
static void SomeFunc() { Bar b; }
};

int main()
{
Foo::SomeFunc(); // will compile
Bar::SomeFunc(); // compiler error
}

Live Example

Deleted default constructor. Objects can still be created... sometimes

When viewing things this way it is easy to say there is complete and utter chaos in the way an object is initialized.

The big difference comes from the type of foo: if it is an aggregate type or not.

It is an aggregate if it has:

  • no user-provided constructors (a deleted or defaulted function does not count as user-provided),
  • no private or protected non-static data members,
  • no brace-or-equal-initializers for non-static data members (since c++11 until (reverted in) c++14)
  • no base classes,
  • no virtual member functions.

So:

  • in scenarios A B D E: foo is an aggregate
  • in scenarios C: foo is not an aggregate
  • scenario F:

    • in c++11 it is not an aggregate.
    • in c++14 it is an aggregate.
    • g++ hasn't implemented this and still treats it as a non-aggregate even in C++14.

      • 4.9 doesn't implement this.
      • 5.2.0 does
      • 5.2.1 ubuntu doesn't (maybe a regression)

The effects of list initialization of an object of type T are:

  • ...
  • If T is an aggregate type, aggregate initialization is performed. This takes care of scenarios A B D E (and F in C++14)
  • Otherwise the constructors of T are considered in two phases:

    • All constructors that take std::initializer_list ...
    • otherwise [...] all constructors of T participate in overload resolution [...] This takes care of C (and F in C++11)
  • ...

:

Aggregate initialization of an object of type T (scenarios A B D E (F c++14)):

  • Each non-static class member, in order appearance in the class definition, is copy-initialized from the corresponding clause of the
    initializer list. (array reference omitted)

TL;DR

All these rules can still seem very complicated and headache inducing. I personally over-simplify this for myself (if I thereby shoot myself in the foot then so be it: I guess I will spend 2 days in the hospital rather than having a couple of dozen days of headaches):

  • for an aggregate each data member is initialized from the elements of the list initializer
  • else call constructor

Doesn't this beat the whole purpose of a deleted constructor?

Well, I don't know about that, but the solution is to make foo not an aggregate. The most general form that adds no overhead and doesn't change the used syntax of the object is to make it inherit from an empty struct:

struct dummy_t {};

struct foo : dummy_t {
foo() = delete;
};

foo f{}; // ERROR call to deleted constructor

In some situations (no non-static members at all, I guess), an alternate would be to delete the destructor (this will make the object not instantiable in any context):

struct foo {
~foo() = delete;
};

foo f{}; // ERROR use of deleted function `foo::~foo()`

This answer uses information gathered from:

  • C++14 value-initialization with deleted constructor

  • What are Aggregates and PODs and how/why are they special?

  • List initialization

  • Aggregate initialization
  • Direct initialization

Many thanks to @M.M who helped correct and improve this post.

(C++) What is better when making a class static: put the constructor as private or delete it publically?

Write the code that best explains what you're doing. If you want the class to be privately constructible, make the constructor private. If you want nobody to be able to construct the class, then delete the constructor.

Pre C++11 equivalent to explicitly deleting constructors ( Type(const Type&) = delete; )

Making the constructor private is the pre-C++11 solution. Your second code is not valid because the copy constructor doesn't have a definition (presuming you don't give it a definition elsewhere). Yeah, it's not the best solution, but that's why = delete was introduced.

You may want to use boost::noncopyable to be more explicit about it, but it only does the same thing.



Related Topics



Leave a reply



Submit