Home > front end >  Why does p3 need a default constructor in this example?
Why does p3 need a default constructor in this example?

Time:04-24

Let's say I have C code like this:

 //main.cpp

#include "p3.h"
#include "tri3.h"

int main()
{
    p3 point1(0.0f, 0.0f, 0.0f);
    p3 point2(1.0f, 0.0f, 0.0f);
    p3 point3(2.0f, 0.0f, 0.0f);

    tri3 triangle(point1, point2, point3);
}

//p3.h

#pragma once

class p3
{
public:
    float _x;
    float _y;
    float _z;
    p3(float x, float y, float z)
    {
        _x = x;
        _y = y;
        _z = z;
    }
};

//tri3.h

#pragma once

#include "p3.h"

class tri3
{
public:
    p3 _p1;
    p3 _p2;
    p3 _p3;
    tri3(p3 p1, p3 p2, p3 p3)
    {
        _p1 = p1;
        _p2 = p2;
        _p3 = p3;
    }
};

The compilation fails with the following error in Visual Studio 2022: error C2512: 'p3': no appropriate default constructor available

When I edit "p3.h" like this, the compilation is succesfull with no errors:

//p3.h

#pragma once

class p3
{
public:
    float _x;
    float _y;
    float _z;
    p3(float x, float y, float z)
    {
        _x = x;
        _y = y;
        _z = z;
    }

    p3() = default; // <-- Adding this makes the program compile just fine
};

In the Microsoft documentation about error C2512, there is an example of an object being created with no arguments and because it has no default constructor, this error occurs. However, in this example, I create my objects by passing all necessary arguments. Why do I still need a default constructor?

CodePudding user response:

    tri3(p3 p1, p3 p2, p3 p3)

The constructor fails to initialize its class's _p1, _p2, and _p3 members, therefore they must have a default constructor.

        _p1 = p1;
        _p2 = p2;
        _p3 = p3;

This is not construction. This is assigning to existing objects. They are already constructed.

To properly construct class members you must use member initialization in the constructor declaration itself.

    tri3(p3 p1, p3 p2, p3 p3) : _p1{p1}, _p2{p2}, _p3{p3}
    {
    }

There are a number of important rules that you must follow, when it comes to properly using member initialization, see your C textbook for more information.

CodePudding user response:

The problem is caused by your tri3 constructor.

Implementing it the way you did, means that p3 _p1 etc are supposed to be default constructed, and then assigned to (in the body of the tri3 constructor).

You should change the implementation like the following to avoid the default construction step for _p1,_p2,_p3:

tri3(p3 p1, p3 p2, p3 p3)
    :_p1(p1), _p2(p2), _p3(p3)
{}

This is called member initializer list: Constructors and member initializer lists

CodePudding user response:

Any object you declare is a candidate whose constructor will be called. If object has default constructor, only data-type and object name is enough, no need anything additional like parentheses. Otherwise, you should carefully design your class whether it requires members initialized.

Mostly compiler warns you about the variables/object that you did not properly initialized.

As I mentioned above, any object you declare will need to be initialized. Compiler will look for the available constructors. If there's a perfect fit, it will be chosen. Otherwise, you'll get an inevitable error like:

error C2512: 'dataType': no appropriate default constructor available

Additionally, trying to fix it by default keyword is not appropriate. As I said, the object you will create may require some objects initialized even if compiler does not notify you.

Some examples:

class Foo
{
public:
   Foo(){};// This is a default constructor
   // You could use Foo() = default; instead. No difference between them
}
// You can safely create it
Foo foo;// ok
Foo foo(); // ok

But if you have something like:

class Bar
{
public:
    Bar(const std::string& string) : _string("test"),_string_(string) ;// both should be here, you cannot use default constructor for this class. 
//(In addition, as _string("test") suggests, it may have an rvalue but the reference type can't)
    {};
    
private:
   // Both should be in initializer list
   const std::string _string;
   std::string& _string_;
}

That was a situation that compiler would warn you about. But if you would have something like:

class FooBar
{
public:
FooBar(){};
private:
int m_number;// some compilers won't even warn you about this
Bar* p_PointerObject;// or this
}

So, sometimes it may seem to work because of some compilers but your program will crash at a point.

  • Related