Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make user defined empty default constructor behave like compiler defined empty constructor

I've been trying to learn C++ for about a month and it still puzzles me. For example this 'easy' code:

class A
{
    int m;
public:
    A() = default;
    int getM() { return m; }
};

class B
{
    int m;
public:
    // empty body and empty initializer list
    B() {} // isn't this the same as "B() = default;" ?
    int getM() { return m; }

};

int main()
{
    A a1;
    A a2{};

    std::cout << "a1.m=" << a1.getM() << "\n";
    std::cout << "a2.m=" << a2.getM() << "\n";

    B b1;
    B b2{};

    std::cout << "b1.m=" << b1.getM() << "\n";
    std::cout << "b2.m=" << b2.getM() << "\n";

    std::cin.ignore();
}

result:

a1.m=...garbage
a2.m=0
b1.m=...garbage
b2.m=...garbage

According to CPP REFERENCE default constructor defined by the compiler does have empty body and empty initializer list. So how the heck does it (in class A) initialize member 'm' to zero when explicitly defined default constructor with empty body and empty initializer list does not. According to cppreference excerpt:

If the implicitly-declared default constructor is not defined as deleted,  it is defined (that is, a function body is generated and compiled)
by the compiler if odr-used, and it has exactly the same effect as a user-defined constructor with empty body and empty initializer list. 

As far as I understand it both constructors should behave exactly the same way. Seems simple yet I do not get it.

like image 558
R-tur Avatar asked Apr 10 '19 17:04

R-tur


People also ask

Can a default constructor be empty?

Providing an empty default constructor( private ) is necessary in those cases when you don't want an object of the class in the whole program. For e.g. the class given below will have a compiler generated default empty constructor as a public member . As a result, you can make an object of such a class.

How do you define a default constructor?

A default constructor is a constructor that either has no parameters, or if it has parameters, all the parameters have default values. If no user-defined constructor exists for a class A and one is needed, the compiler implicitly declares a default parameterless constructor A::A() .

Do you need to define empty constructor?

An empty constructor is required when we need to create a new instance via reflection by our framework. If we don't create any other constructor with arguments for the class, we don't need to create an empty constructor because one default will already be present.

When there is no constructor defined compiler provides?

If the class declares no constructors explicitly, the compiler generates a default no-arg constructor that just invokes the superclass's no-arg constructor. As with any other constructor, the compiler creates an <init>() method in the class file that corresponds to this default constructor. (5) is correct.


1 Answers

Here's the basic idea. Both A a2{}; and B b2{}; will perform what is called "value initialization" on the two objects. However, the way value initialization behaves depends on how those types are defined.

B is an object which has a user-provided default constructor. "User-provided" being the term for when you provide a body for the default constructor. Because of that, value initialization will call the default constructor. That default constructor does not initialize its members, so the members remain uninitialized.

A is an object which does not have a user-provided default constructor. Nor does it have any other user-provided constructors. And the default constructor is not deleted either. And there are no default member initializers in A. Given all of that, value initialization will perform zero initialization on the object. Which means that it will write all zeros to the memory for that object before it comes into existence.

That's what the rules say; the two do not behave the same, nor are they meant to. Nor is there anything you can do to make a user-provided default constructor act like a defaulted default constructor in all cases. You can make the user-provided constructor value initialize its members, but it would do so all the time, even if you use default initialization (B b1;, for example).

Why do the rules say that? Because = default is not supposed to be equivalent to an empty constructor body. Indeed, being different is why = default exists as a feature.

When you = default your default constructor, you are saying "generate the default constructor as you normally would". This is important, because there are things you can do which actively prevent the compiler from generating a default constructor for you. If you specify other constructors (which are not copy/move constructors), the compiler will not automatically generate one. So by using = default syntax, you're telling the compiler that you want the generated default constructor.

By contrast, if you make an empty body in your default constructor, you are saying something totally different. You are explicitly saying, "If a user calls my default constructor, I want my members to be default-initialized." That's what it means when you have an empty member initializer list in a constructor, after all. So that's what it should do.

If = default and an empty body behaved the same, there would be no way for you to get that behavior, to say that you want default initialization of your members no matter what.

Basically, Cppreference's statement is completely wrong; it does not have "exactly the same effect as a user-defined constructor with empty body and empty initializer list". Nor is it supposed to.


If you want to understand the thinking of value initialization a bit further, consider this.

int i{};

That is guaranteed to produce a value of 0 for i. It is therefore reasonable that this:

struct S{int i;};
S s{};

Should also produce a value of 0 for s.i. How does that happen? Because value initialization will zero-initialize s.

So how does a user say that they don't want that, or want some special form of initialization? You communicate that the same way you communicate everything else: you add a constructor. Specifically, a default constructor that does the form of initialization you want.

like image 192
Nicol Bolas Avatar answered Oct 26 '22 01:10

Nicol Bolas