ash
ash

Reputation: 3474

Why explicitly delete the constructor instead of making it private?

When/why would I want to explicitly delete my constructor? Assuming the reason is to prevent its usage, why not just make it private?

class Foo
{ 
  public: 
    Foo() = delete; 
};

Upvotes: 134

Views: 110666

Answers (4)

Benjamin Buch
Benjamin Buch

Reputation: 6113

tl;dr

Explicit delete always works and gives more understandable error messages. Private declaration should no longer be used to prevent calls!

Detailed explanation

If you declare a function private, then it can still be called. In the case of a constructor, for example, from a static or a friend function.

With an explicit deletion of a function you express that it must never be used. This leads especially to clearly understandable error messages when trying to call such a function.

In the following class we do not have a default constructor because there is no meaningful way to implement it. The class contains a reference and it needs some object to point to.

class Foo {
private:
    int& ref_;

public:
    Foo(int& ref) : ref_(ref) {}
};

int main() {
    Foo();
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:10:9: error: no matching function for call to ‘Foo::Foo()’
   10 |     Foo();
      |         ^
main.cpp:6:5: note: candidate: ‘Foo::Foo(int&)’
    6 |     Foo(int& ref) : ref_(ref) {}
      |     ^~~
main.cpp:6:5: note:   candidate expects 1 argument, 0 provided
main.cpp:1:7: note: candidate: ‘constexpr Foo::Foo(const Foo&)’
    1 | class Foo {
      |       ^~~
main.cpp:1:7: note:   candidate expects 1 argument, 0 provided
main.cpp:1:7: note: candidate: ‘constexpr Foo::Foo(Foo&&)’
main.cpp:1:7: note:   candidate expects 1 argument, 0 provided

Deleting the constructor makes the error message short and understandable.

class Foo {
private:
    int& ref_;

public:
    Foo() = delete;
    Foo(int& ref) : ref_(ref) {}
};

int main() {
    Foo();
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:11:9: error: use of deleted function ‘Foo::Foo()’
   11 |     Foo();
      |         ^
main.cpp:6:5: note: declared here
    6 |     Foo() = delete;
      |     ^~~

If we solve this via a private declaration (without definition, which is impossible), the message looks similar at first.

class Foo {
private:
    int& ref_;

    Foo();

public:
    Foo(int& ref) : ref_(ref) {}
};

int main() {
    Foo();
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:12:9: error: ‘Foo::Foo()’ is private within this context
   12 |     Foo();
      |         ^
main.cpp:5:5: note: declared private here
    5 |     Foo();
      |     ^~~

This works great as long as you don't call the constructor from a context where the private section of the class is accessible. As written above, this can be for example a static or a friendly function. In principle, it can also be a normal function, although this use case is rather rare.

class Foo {
private:
    int& ref_;

    Foo();

public:
    Foo(int& ref) : ref_(ref) {}

    static Foo create() {
        return Foo(); // compiles fine
    }

    void foo() {
        Foo(); // compiles fine
    }

    friend void bar();
};

void bar() {
    Foo(); // compiles fine
}

int main() {}
g++ -c -o main.o main.cpp

This compiles completely without problems, the compile simply assumes that there will be a definition of Foo::Foo() somewhere else. As soon as the linker has to make an executable out of it, it will report the missing definition.

$ g++ main.o
# or as one step with compilation and linking
$ g++ main.cpp
/usr/bin/ld: /tmp/ccnhLDsv.o: in function `bar()':
main.cpp:(.text+0x23): undefined reference to `Foo::Foo()'
collect2: error: ld returned 1 exit status

Such errors are often extremely difficult to debug because they are stuck anywhere in the code base and you have no clue which file, let alone which line, the error is in.

An explicit delete, on the other hand, provides three precise error messages at the three locations where the errors are located.

class Foo {
private:
    int& ref_;

public:
    Foo() = delete;
    Foo(int& ref) : ref_(ref) {}

    static Foo create() {
        return Foo(); // error
    }

    void foo() {
        Foo(); // error
    }

    friend void bar();
};

void bar() {
    Foo(); // error
}

int main() {}
$ g++ main.cpp
main.cpp: In static member function ‘static Foo Foo::create()’:
main.cpp:10:20: error: use of deleted function ‘Foo::Foo()’
   10 |         return Foo(); // error
      |                    ^
main.cpp:6:5: note: declared here
    6 |     Foo() = delete;
      |     ^~~
main.cpp: In member function ‘void Foo::foo()’:
main.cpp:14:13: error: use of deleted function ‘Foo::Foo()’
   14 |         Foo(); // error
      |             ^
main.cpp:6:5: note: declared here
    6 |     Foo() = delete;
      |     ^~~
main.cpp: In function ‘void bar()’:
main.cpp:21:9: error: use of deleted function ‘Foo::Foo()’
   21 |     Foo(); // error
      |         ^
main.cpp:6:5: note: declared here
    6 |     Foo() = delete;
      |     ^~~

Additional information

void foo(int need_integer) {}

int main() {
    foo(5.4); // might trigger a warning, but compiles
}

Note that delete can also be used for normal functions. For example, to prevent implicit conversions.

void foo(int need_integer) {}
void foo(double) = delete;

int main() {
    foo(5);   // okay
    foo(5.4); // error
}
$ g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:6:8: error: use of deleted function ‘void foo(double)’
    6 |     foo(5.4); // error
      |     ~~~^~~~~
main.cpp:2:6: note: declared here
    2 | void foo(double) = delete;
      |      ^~~

Upvotes: 5

Peter VARGA
Peter VARGA

Reputation: 5186

Why explicitly delete the constructor?

Another reason:

I use delete when I want to assure that a class is called with an initializer. I consider it as a very elegant way to achieve this without runtime checks.

The C++ compiler does this check for you.

class Foo
{
   public:
       Foo() = delete;
       Foo(int bar) : m_bar(bar) {};
   private:
       int m_bar;
};

This - very simplified - code assures that there is no instantiation (default construction) like this: Foo foo;

Upvotes: 28

gybacsi
gybacsi

Reputation: 61

I've met with default ctors declared as 'deleted' in the source code of LLVM (in AlignOf.h for instance). The associated class templates are usually in a special namespace called 'llvm::detail'. The whole purpose there I think was that they considered that class only as a helper class. They never intended to instantiate them; only to use them within the context of other class templates with some metaprogramming tricks that run in compile time.

Eg. there's this AlignmentCalcImpl class template which is used only within another class template called AlignOf as a parameter for the sizeof(.) operator. That expression can be evaluated in compile time; and there's no need to instantiate the template -> so why not declare the default ctor delete to express this intention.

But it's only my assumption.

Upvotes: 2

Luchian Grigore
Luchian Grigore

Reputation: 258568

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.

Upvotes: 132

Related Questions