为什么 p3 在这个例子中需要一个默认构造函数?

Why does p3 need a default constructor in this example?

假设我有这样的 C++ 代码:

 //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;
    }
};

编译失败,在 Visual Studio 2022 中出现以下错误:error C2512: 'p3': no appropriate default constructor available

当我这样编辑“p3.h”时,编译成功,没有错误:

//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
};

在关于error C2512的微软文档中,有一个对象创建时没有参数的例子,因为它没有默认构造函数,所以会出现这个错误。然而,在这个例子中,我通过传递所有必要的参数来创建我的对象。为什么我还需要默认构造函数?

    tri3(p3 p1, p3 p2, p3 p3)

构造函数无法初始化其 class 的 _p1_p2_p3 成员,因此它们必须具有默认构造函数。

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

这不是施工。这是分配给 existing 对象。它们已经建成。

要正确构造 class 成员,您必须在构造函数声明本身中使用 成员初始化

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

在正确使用成员初始化方面,您必须遵循许多重要规则,有关详细信息,请参阅 C++ 教科书。

问题是由您的 tri3 构造函数引起的。

按照您的方式实现它,意味着 p3 _p1 等应该是默认构造的,然后分配给(在 tri3 构造函数的主体中)。

您应该像下面这样更改实现以避免 _p1_p2_p3:

的默认构造步骤
tri3(p3 p1, p3 p2, p3 p3)
    :_p1(p1), _p2(p2), _p3(p3)
{}

这称为成员初始值设定项列表Constructors and member initializer lists

您声明的任何对象都是将调用其构造函数的候选者。 如果对象有默认构造函数,只需要 data-type 和对象名就足够了,不需要任何额外的东西,比如括号。否则,你应该仔细设计你的 class 是否需要成员初始化。

大多数情况下编译器会警告您 variables/object 您没有正确初始化。

正如我上面提到的,您声明的任何对象都需要初始化。编译器将寻找可用的构造函数。如果有一个完美的契合,它将被选中。否则,您将不可避免地遇到错误,例如:

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

此外,尝试通过 default 关键字修复它是不合适的。正如我所说,您将创建的对象可能需要初始化一些对象,即使编译器没有通知您。

一些例子:

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

但是如果你有类似的东西:

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_;
}

在这种情况下,编译器会警告您。 但是如果你有这样的东西:

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

因此,有时它似乎可以工作,因为某些编译器,但您的程序会在某一时刻崩溃。