尝试理解默认构造函数和成员初始化

发布于 2025-01-15 12:53:25 字数 1537 浏览 3 评论 0原文

我习惯于在类构造函数中初始化成员变量,但我想我应该检查默认构造函数是否设置了默认值。我的测试是使用 C++ 20 语言标准使用 Visual Studio 2022 进行的。结果让我很困惑:

#include <iostream>

class A
{
public:
    double r;
};

class B
{
public:
    B() = default;
    double r;
};

class C
{
public:
    C() {}
    double r;
};

int main()
{
    A a1;
    std::cout << a1.r << std::endl; // ERROR: uninitialized local variable 'a1' used

    A a2();
    std::cout << a2.r << std::endl; // ERROR: left of '.r' must have class/struct/union

    A* pa1 = new A;
    std::cout << pa1->r << std::endl; // output: -6.27744e+66

    A* pa2 = new A();
    std::cout << pa2->r << std::endl; // output: 0

    B b1;
    std::cout << b1.r << std::endl; // ERROR: uninitialized local variable 'b1' used

    B b2();
    std::cout << b2.r << std::endl; // ERROR: left of '.r' must have class/struct/union

    B* pb1 = new B;
    std::cout << pb1->r << std::endl; // output: -6.27744e+66

    B* pb2 = new B();
    std::cout << pb2->r << std::endl; // output: 0

    C c1;
    std::cout << c1.r << std::endl;  // output: -9.25596e+61

    C c2();
    std::cout << c2.r << std::endl; // ERROR: left of '.r' must have class/struct/union

    C* pc1 = new C;
    std::cout << pc1->r << std::endl; // output: -6.27744e+66

    C* pc2 = new C();
    std::cout << pc2->r << std::endl; // output: -6.27744e+66
}

感谢任何能够启发我的人。

I am used to initialising member variables in class constructors, but I thought I'd check out if default values are set by default constructors. My tests were with Visual Studio 2022 using the C++ 20 language standard. The results confused me:

#include <iostream>

class A
{
public:
    double r;
};

class B
{
public:
    B() = default;
    double r;
};

class C
{
public:
    C() {}
    double r;
};

int main()
{
    A a1;
    std::cout << a1.r << std::endl; // ERROR: uninitialized local variable 'a1' used

    A a2();
    std::cout << a2.r << std::endl; // ERROR: left of '.r' must have class/struct/union

    A* pa1 = new A;
    std::cout << pa1->r << std::endl; // output: -6.27744e+66

    A* pa2 = new A();
    std::cout << pa2->r << std::endl; // output: 0

    B b1;
    std::cout << b1.r << std::endl; // ERROR: uninitialized local variable 'b1' used

    B b2();
    std::cout << b2.r << std::endl; // ERROR: left of '.r' must have class/struct/union

    B* pb1 = new B;
    std::cout << pb1->r << std::endl; // output: -6.27744e+66

    B* pb2 = new B();
    std::cout << pb2->r << std::endl; // output: 0

    C c1;
    std::cout << c1.r << std::endl;  // output: -9.25596e+61

    C c2();
    std::cout << c2.r << std::endl; // ERROR: left of '.r' must have class/struct/union

    C* pc1 = new C;
    std::cout << pc1->r << std::endl; // output: -6.27744e+66

    C* pc2 = new C();
    std::cout << pc2->r << std::endl; // output: -6.27744e+66
}

Thanks to anyone who can enlighten me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

驱逐舰岛风号 2025-01-22 12:53:25

让我们看看您给定的示例中具体情况发生了什么。

案例 1

这里我们考虑以下语句:

A a1; //this creates a variable named a1 of type A using the default constrcutor
std::cout << a1.r << std::endl; //this uses the uninitialized data member r which leads to undefined behavior

在上面的代码片段中,第一个语句使用 default ctor 创建一个名为 a1、类型为 A 的变量 < code>A::A() 由编译器合成。这意味着数据成员r默认初始化。由于r是内置类型,因此它将具有不确定的值。在编写上面代码段中显示的第二个语句时使用此未初始化变量是未定义行为

案例 2

这里我们考虑以下语句:

A a2(); //this is a function declaration 
std::cout << a2.r << std::endl; //this is not valid since a2 is the name of a function

上面代码片段中的第一个语句,声明一个名为 a2 的函数,该函数不带任何参数,返回类型为 A< /代码>。也就是说,第一条语句实际上是一个函数声明。现在,在第二条语句中,您尝试访问名为 a2 的函数的数据成员 r ,这没有任何意义,因此您会收到上述错误。

案例 3

这里我们考虑以下语句:

A* pa1 = new A; 
std::cout << pa1->r << std::endl; // output: -6.27744e+66

上面代码片段中的第一条语句具有以下效果:

  1. 由于 new A 使用 new A 在堆上创建了一个 A 类型的未命名对象编译器默认构造函数 A::A() 合成。此外,我们还得到了一个指向这个未命名对象的指针。
  2. 接下来,我们在上面步骤 1 中获得的指针将用作 pa1初始化器。也就是说,创建了一个名为 pa1 的指向 A 的指针,并通过指向我们在上面步骤 1 中获得的未命名对象的指针进行初始化。

由于使用了默认构造函数(请参阅步骤 1),这意味着未命名对象的数据成员 r默认初始化。并且由于数据成员r 是内置类型,这意味着它具有不确定的值。在上述代码片段的第二个语句中使用这个未初始化的数据成员r未定义行为。这就是为什么你会得到一些垃圾值作为输出。

案例 4

这里我们考虑以下语句:

A* pa2 = new A();
std::cout << pa2->r << std::endl;

上面代码片段的第一条语句具有以下效果:

  1. 由于表达式 new A()<,创建了 A 类型的未命名对象/代码>。但这一次,由于您使用了括号 () 并且类 A 没有用户提供的默认构造函数,这意味着将发生值初始化 。这本质上意味着数据成员r将被零初始化。这就是为什么/如何在上面代码片段的第二个语句中获得 0 输出。此外,指向该未命名对象的指针将作为结果返回。


  2. 接下来,创建一个名为 pa2 的指向 A 的指针,并使用我们在上面步骤 1 中获得的未命名对象的指针进行初始化。


接下来的 4 个与 B 类相关的语句也发生了完全相同的情况。因此,我不会讨论与 B 类相关的接下来 4 个语句,因为我们不会从中学到任何新内容。对于他们来说,与上面描述的前 4 条语句一样,也会发生同样的事情。


现在将考虑与类C相关的语句。我们不会跳过这 4 条语句,因为对于类 C 来说,有一个用户定义的默认构造函数。

语句 5

这里我们考虑以下语句:

C c1;
std::cout << c1.r << std::endl;

上面代码片段的第一条语句使用用户提供的默认构造函数创建一个名为c1、类型为C的变量A::A()。由于该用户提供的默认构造函数不执行任何操作,因此数据成员 r 未初始化,我们得到与 A a1; 讨论的相同行为。也就是说,在第二个语句中使用这个未初始化的变量是未定义行为

语句 6

这里我们考虑以下语句:

C c2();
std::cout << c2.r << std::endl;

上面代码片段中的第一个语句是函数声明。因此,您将得到与 A 类相同的行为/错误。

语句 7

这里我们考虑以下语句:

C* pc1 = new C;
std::cout << pc1->r << std::endl;

上面代码片段中的第一条语句具有以下效果:

  1. 使用用户提供的默认构造函数在堆上创建 C 类型的未命名对象 A::A() 由于表达式 new A。由于用户提供的默认构造函数不执行任何操作,因此数据成员 r 未初始化。此外,我们得到一个指向这个未命名对象的指针作为结果。

  2. 接下来,创建一个名为 pc1 的指向 C 的指针,并由 pionter 初始化为我们在步骤 1 中获得的未命名对象。

现在上面代码片段中的第二个语句使用未初始化的数据成员 r 这是未定义的行为,并解释了为什么您会得到一些垃圾值作为输出。

语句 8

这里我们考虑以下语句:

C* pc2 = new C();
std::cout << pc2->r << std::endl;

上面代码片段的第一条语句具有以下效果:

  1. 由于 new C() 在堆上创建了 C 类型的未命名对象。现在,由于您指定了括号 () 这将进行值初始化。但是因为这次我们有一个用户提供的默认构造函数,所以值初始化与默认初始化相同,这将使用用户提供的默认构造函数来完成。由于用户提供的默认构造函数不执行任何操作,因此数据成员 r 将保持未初始化状态。此外,我们得到一个指向未命名对象的指针作为结果。

  2. 接下来,创建一个名为 pc2 的指向 C 的指针,并由 pionter 初始化为我们在上面步骤 1 中获得的未命名对象。

现在,上面代码片段中的第二条语句使用未初始化的数据成员r,这是未定义的行为,并解释了为什么您会得到一些垃圾值作为输出。

Lets see what is happening on case by case basis in your given example.

Case 1

Here we consider the statements:

A a1; //this creates a variable named a1 of type A using the default constrcutor
std::cout << a1.r << std::endl; //this uses the uninitialized data member r which leads to undefined behavior

In the above snippet, the first statement creates a variable named a1 of type A using the default ctor A::A() synthesized by the compiler. This means that the data member r will default initialized. And since r is of built-in type, it will have undeterminate value. Using this uninitilized variable which you do when you wrote the second statement shown in the above snippet is undefined behavior.

Case 2

Here we consider the statements:

A a2(); //this is a function declaration 
std::cout << a2.r << std::endl; //this is not valid since a2 is the name of a function

The first statement in the above snippet, declares a function named a2 that takes no parameters and has the return type of A. That is, the first statement is actually a function declaration. Now, in the second statement you're trying to access a data member r of the function named a2 which doesn't make any sense and hence you get the mentioned error.

Case 3

Here we consider the statements:

A* pa1 = new A; 
std::cout << pa1->r << std::endl; // output: -6.27744e+66

The first statement in the above snippet has the following effects:

  1. an unnamed object of type A is created on the heap due to new A using the default constructor A::A() synthesized by the compiler. Moreover, we also get a pointer to this unnamed object as a result.
  2. Next, the pointer that we got in step 1 above, is used as an initializer for pa1. That is, a pointer to A named pa1 is created and is initialized by the pointer to the unnamed object that we got in step 1 above.

Since, the default constructor was used(see step 1) this means that the data member r of the unnamed object is default initilaized. And since the data member r is of built in type, this implies that it has indeterminate value. And using this uninitialized data member r which you do in the second statement of the above code snippet, is undefined behavior. This is why you get some garbage value as output.

Case 4

Here we consider the statements:

A* pa2 = new A();
std::cout << pa2->r << std::endl;

The first statement of the above snippet has the following effects:

  1. An unnamed object of type A is created due to the expression new A(). But this time since you have used parenthesis () and since class A does not have an user provided default constructor, this means value initialization will happen. This essentially means that the data member r will be zero initialized. This is why/how you get the output as 0 in the second statement of the above snippet. Moreover, a pointer to this unnamed object is returned as the result.

  2. Next, a pointer to A named pa2 is created and is initialized using the pointer to the unnamed object that we got in step 1 above.


Exactly the same thing happens with the next 4 statements related to class B. So i am not discussing the next 4 statements that are related to class B since we will learn nothing new from them. The same thing will happen for them as for the previous 4 statement described above.


Now will consider the statements related to class C. We're not skipping over these 4 statements because for class C there is a user-defined default constructor.

Statement 5

Here we consider the statements:

C c1;
std::cout << c1.r << std::endl;

The first statement of the above snippet creates a variable named c1 of type C using the user provided default constructor A::A(). Since this user provided default constructor doesn't do anything, the data member r is left uninitialized and we get the same behavior as we discussed for A a1;. That is, using this uninitialized variable which you do in the second statement is undefined behavior.

Statement 6

Here we consider the statements:

C c2();
std::cout << c2.r << std::endl;

The first statement in the above snippet is a function declaration. Thus you'll get the same behavior/error that we got for class A.

Statement 7

Here we consider the statements:

C* pc1 = new C;
std::cout << pc1->r << std::endl;

The first statement in the above snippet has the following effects:

  1. An unnamed object of type C is created on the heap using the user provided default constructor A::A() due to the expression new A. And since the user provide default constructor does nothing, the data member r is left uninitialized. Moreover, we get a pointer to this unnamed object as result.

  2. Next, a pointer to C named pc1 is created and is initialized by the pionter to unnamed object that we got in step 1.

Now the second statement in the above snippet, uses uninitialized data member r which is undefined behavior and explains why you are getting some garbage value as output.

Statement 8

Here we consider the statements:

C* pc2 = new C();
std::cout << pc2->r << std::endl;

The first statement of the above snippet has the following effects:

  1. An unnamed object of type C is created on the heap due to new C(). Now since you have specificed parenthesis () this will do value-initialization. But because this time we've a user-provide default constructor, value-initialization is the same as default-initialization which will be done using the user-provide default constructor. And since the user provide default constructor does nothing, the data member r will be left uninitialized. Moreover, we get a pointer to the unnamed object as result.

  2. Next, a pointer to C named pc2 is created and is initialized by the pionter to unnamed object that we got in step 1 above.

Now the second statement in the above snippet, uses uninitialized data member r which is undefined behavior and explains why you are getting some garbage value as output.

莫言歌 2025-01-22 12:53:25

A a2();B b2();C c2(); 也可以解析为返回 的函数声明A/B/C 参数列表为空。这种解释是首选,因此您声明的是函数,而不是变量。这也称为“最令人烦恼的解析”问题。

没有一个默认构造函数(包括 A 的隐式构造函数)正在初始化 r,因此它将具有不确定的值。读取该值将导致未定义的行为。

例外情况是 A* pa2 = new A();B* pb2 = new B();() 初始化器执行值初始化。在这两种情况下,值初始化的效果是整个对象将被零初始化,因为 AB 都有一个不是用户提供的默认构造函数。 (第一次声明时的默认构造函数不算是用户提供的。)

对于 C 来说,这不适用,因为 C 的默认构造函数是用户提供的,并且因此值初始化只会导致默认初始化,调用默认构造函数,而不会初始化r

A a2();, B b2(); and C c2(); could also be parsed as declarations of functions returning A/B/C with empty parameter list. This interpretation is preferred and so you are declaring functions, not variables. This is also known as the "most vexing parse" issue.

None of the default constructors (including the implicit one of A) are initializing r, so it will have an indeterminate value. Reading that value will cause undefined behavior.

An exception are A* pa2 = new A(); and B* pb2 = new B();. The () initializer does value-initialization. The effect of value-initialization is in these two cases that the whole object will be zero-initialized, because both A and B have a default constructor that is not user-provided. (Defaulted on first declaration doesn't count as user-provided.)

In case of C this doesn't apply, because C's default constructor is user-provided and therefore value-initialization will only result in default-initialization, calling the default constructor, which doesn't initialize r.

蝶…霜飞 2025-01-22 12:53:25
MyType name(); // This is treated as a function declaration
MyType name{}; // This is the correct way
MyType name(); // This is treated as a function declaration
MyType name{}; // This is the correct way
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文