尝试理解默认构造函数和成员初始化
我习惯于在类构造函数中初始化成员变量,但我想我应该检查默认构造函数是否设置了默认值。我的测试是使用 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
让我们看看您给定的示例中具体情况发生了什么。
案例 1
这里我们考虑以下语句:
在上面的代码片段中,第一个语句使用 default ctor 创建一个名为
a1
、类型为A
的变量 < code>A::A() 由编译器合成。这意味着数据成员r
将默认初始化。由于r
是内置类型,因此它将具有不确定的值。在编写上面代码段中显示的第二个语句时使用此未初始化变量是未定义行为。案例 2
这里我们考虑以下语句:
上面代码片段中的第一个语句,声明一个名为
a2
的函数,该函数不带任何参数,返回类型为A< /代码>。也就是说,第一条语句实际上是一个函数声明。现在,在第二条语句中,您尝试访问名为
a2
的函数的数据成员r
,这没有任何意义,因此您会收到上述错误。案例 3
这里我们考虑以下语句:
上面代码片段中的第一条语句具有以下效果:
new A
使用new A
在堆上创建了一个A
类型的未命名对象编译器默认构造函数A::A()
合成。此外,我们还得到了一个指向这个未命名对象的指针。pa1
的初始化器。也就是说,创建了一个名为pa1
的指向A
的指针,并通过指向我们在上面步骤 1 中获得的未命名对象的指针进行初始化。由于使用了默认构造函数(请参阅步骤 1),这意味着未命名对象的数据成员
r
已默认初始化。并且由于数据成员r
是内置类型,这意味着它具有不确定的值。在上述代码片段的第二个语句中使用这个未初始化的数据成员r
是未定义行为。这就是为什么你会得到一些垃圾值作为输出。案例 4
这里我们考虑以下语句:
上面代码片段的第一条语句具有以下效果:
由于表达式
new A()<,创建了
A
类型的未命名对象/代码>。但这一次,由于您使用了括号()
并且类A
没有用户提供的默认构造函数,这意味着将发生值初始化 。这本质上意味着数据成员r
将被零初始化。这就是为什么/如何在上面代码片段的第二个语句中获得0
输出。此外,指向该未命名对象的指针将作为结果返回。接下来,创建一个名为
pa2
的指向A
的指针,并使用我们在上面步骤 1 中获得的未命名对象的指针进行初始化。接下来的 4 个与 B 类相关的语句也发生了完全相同的情况。因此,我不会讨论与 B 类相关的接下来 4 个语句,因为我们不会从中学到任何新内容。对于他们来说,与上面描述的前 4 条语句一样,也会发生同样的事情。
现在将考虑与类
C
相关的语句。我们不会跳过这 4 条语句,因为对于类C
来说,有一个用户定义的默认构造函数。语句 5
这里我们考虑以下语句:
上面代码片段的第一条语句使用用户提供的默认构造函数创建一个名为
c1
、类型为C
的变量A::A()
。由于该用户提供的默认构造函数不执行任何操作,因此数据成员r
未初始化,我们得到与A a1;
讨论的相同行为。也就是说,在第二个语句中使用这个未初始化的变量是未定义行为。语句 6
这里我们考虑以下语句:
上面代码片段中的第一个语句是函数声明。因此,您将得到与
A
类相同的行为/错误。语句 7
这里我们考虑以下语句:
上面代码片段中的第一条语句具有以下效果:
使用用户提供的默认构造函数在堆上创建
C
类型的未命名对象A::A()
由于表达式new A
。由于用户提供的默认构造函数不执行任何操作,因此数据成员 r 未初始化。此外,我们得到一个指向这个未命名对象的指针作为结果。接下来,创建一个名为
pc1
的指向C
的指针,并由 pionter 初始化为我们在步骤 1 中获得的未命名对象。现在上面代码片段中的第二个语句使用未初始化的数据成员
r
这是未定义的行为,并解释了为什么您会得到一些垃圾值作为输出。语句 8
这里我们考虑以下语句:
上面代码片段的第一条语句具有以下效果:
由于
new C() 在堆上创建了
。现在,由于您指定了括号C
类型的未命名对象()
这将进行值初始化。但是因为这次我们有一个用户提供的默认构造函数,所以值初始化与默认初始化相同,这将使用用户提供的默认构造函数来完成。由于用户提供的默认构造函数不执行任何操作,因此数据成员 r 将保持未初始化状态。此外,我们得到一个指向未命名对象的指针作为结果。接下来,创建一个名为
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:
In the above snippet, the first statement creates a variable named
a1
of typeA
using the default ctorA::A()
synthesized by the compiler. This means that the data memberr
will default initialized. And sincer
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:
The first statement in the above snippet, declares a function named
a2
that takes no parameters and has the return type ofA
. That is, the first statement is actually a function declaration. Now, in the second statement you're trying to access a data memberr
of the function nameda2
which doesn't make any sense and hence you get the mentioned error.Case 3
Here we consider the statements:
The first statement in the above snippet has the following effects:
A
is created on the heap due tonew A
using the default constructorA::A()
synthesized by the compiler. Moreover, we also get a pointer to this unnamed object as a result.pa1
. That is, a pointer toA
namedpa1
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 memberr
is of built in type, this implies that it has indeterminate value. And using this uninitialized data memberr
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:
The first statement of the above snippet has the following effects:
An unnamed object of type
A
is created due to the expressionnew A()
. But this time since you have used parenthesis()
and since classA
does not have an user provided default constructor, this means value initialization will happen. This essentially means that the data memberr
will be zero initialized. This is why/how you get the output as0
in the second statement of the above snippet. Moreover, a pointer to this unnamed object is returned as the result.Next, a pointer to
A
namedpa2
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 classB
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 classC
there is a user-defined default constructor.Statement 5
Here we consider the statements:
The first statement of the above snippet creates a variable named
c1
of typeC
using the user provided default constructorA::A()
. Since this user provided default constructor doesn't do anything, the data memberr
is left uninitialized and we get the same behavior as we discussed forA a1;
. That is, using this uninitialized variable which you do in the second statement is undefined behavior.Statement 6
Here we consider the statements:
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:
The first statement in the above snippet has the following effects:
An unnamed object of type
C
is created on the heap using the user provided default constructorA::A()
due to the expressionnew A
. And since the user provide default constructor does nothing, the data memberr
is left uninitialized. Moreover, we get a pointer to this unnamed object as result.Next, a pointer to
C
namedpc1
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:
The first statement of the above snippet has the following effects:
An unnamed object of type
C
is created on the heap due tonew 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 memberr
will be left uninitialized. Moreover, we get a pointer to the unnamed object as result.Next, a pointer to
C
namedpc2
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.A a2();
、B b2();
和C c2();
也可以解析为返回的函数声明A
/B
/C
参数列表为空。这种解释是首选,因此您声明的是函数,而不是变量。这也称为“最令人烦恼的解析”问题。没有一个默认构造函数(包括
A
的隐式构造函数)正在初始化r
,因此它将具有不确定的值。读取该值将导致未定义的行为。例外情况是
A* pa2 = new A();
和B* pb2 = new B();
。()
初始化器执行值初始化。在这两种情况下,值初始化的效果是整个对象将被零初始化,因为A
和B
都有一个不是用户提供的默认构造函数。 (第一次声明时的默认构造函数不算是用户提供的。)对于
C
来说,这不适用,因为C
的默认构造函数是用户提供的,并且因此值初始化只会导致默认初始化,调用默认构造函数,而不会初始化r
。A a2();
,B b2();
andC c2();
could also be parsed as declarations of functions returningA
/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 initializingr
, so it will have an indeterminate value. Reading that value will cause undefined behavior.An exception are
A* pa2 = new A();
andB* 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 bothA
andB
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, becauseC
's default constructor is user-provided and therefore value-initialization will only result in default-initialization, calling the default constructor, which doesn't initializer
.