为啥构造函数被调用两次
Posted
技术标签:
【中文标题】为啥构造函数被调用两次【英文标题】:Why constructor is being called twice为什么构造函数被调用两次 【发布时间】:2013-09-27 18:05:57 【问题描述】:不明白怎么constructors work
?
我在这里声明了一个对象obj2
。调用构造函数abc()
,完全没问题。
但是当我分配时
obj2 = 100
为什么编译器允许将整数初始化为类对象?如果它完全允许,那么它如何销毁对象以及它如何调用另一个参数化构造函数。
现在我还有一个问题,为什么 destructor
只被调用一次,因为有 two
对象?
我还有一个疑问,编译器是 not doing anything
和 default constructor
那么为什么默认构造函数是 required
?
class abc
public:
int a, b;
abc()
a = 0; b = 0;
abc(int x)
a = x;
~abc()
std::cout << "Destructor Called\n";
;
int main()
abc obj1;
cout << "OBJ1 " << obj1.a << "..." << obj1.b << "\n";
abc obj2;
cout << "OBJ2 " << obj2.a << "..." << obj2.b << "\n";
obj2 = 100;
cout << "OBJ2 " << obj2.a << "\n";
system("pause");
return 0;
output:
OBJ1 0...0
OBJ2 0...0
Destructor Called
OBJ2 100
【问题讨论】:
【参考方案1】:但是当我分配 obj2 = 100 时,编译器如何允许将整数初始化为类对象?
这是因为当您执行以下操作时:
obj2 = 100;
这个会先调用abc(int x)
生成一个类的对象,然后调用默认的复制赋值操作符(因为没有提供用户定义)给现有的obj2
赋值100。赋值后,临时对象被销毁。
如果您不希望这种效果,请将构造函数标记为explict
以避免隐式调用。
explicit abc(int x)
//do something
【讨论】:
【参考方案2】: obj2 = 100;
您定义了一个采用int
的构造函数。这允许从 int 到 abc
的隐式转换。这需要创建一个新对象。它不只是通过调用构造函数神奇地在现有对象中设置一个字段;构造函数构造新对象。
编辑:来自@Steve Jessop 的正确事件顺序
创建一个新实例,然后将其复制分配给原始实例,然后销毁临时实例(不是原始实例)。复制赋值确实神奇地设置了现有对象中的两个字段。
【讨论】:
“第一个实例被销毁并创建了一个新实例来代替原来的实例”——这是不对的。创建一个新实例,然后将其复制分配给原始实例,然后销毁临时实例(不是原始实例)。复制赋值确实神奇地设置了现有对象中的两个字段。 @SteveJessop:好电话【参考方案3】:让我们表演和讲述,让我们为所有特殊成员提供乐器:
#include <iostream>
class abc
public:
int a, b;
abc()
std::cout << "Default constructor\n"; a = 0; b = 0;
abc(int x)
std::cout << "Int constructor\n"; a = x;
abc(abc const& other): a(other.a), b(other.b)
std::cout << "Copy constructor (" << a << ", " << b << ")\n";
abc& operator=(abc const& other)
std::cout << "Assignment operator (" << a << ", " << b << ") = (" << other.a << ", " << other.b << ")\n";
a = other.a;
b = other.b;
return *this;
~abc()
std::cout << "Destructor Called\n";
;
int main()
abc obj1;
std::cout << "OBJ1 " << obj1.a << "..." << obj1.b << "\n";
abc obj2;
std::cout << "OBJ2 " << obj2.a << "..." << obj2.b << "\n";
obj2 = 100;
std::cout << "OBJ2 " << obj2.a << "\n";
return 0;
我们得到this output:
Default constructor
OBJ1 0...0
Default constructor
OBJ2 0...0
Int constructor
Assignment operator (0, 0) = (100, 0)
Destructor Called
OBJ2 100
Destructor Called
Destructor Called
所以,让我们将它们与线源相协调:
int main()
abc obj1;
// Default constructor
std::cout << "OBJ1 " << obj1.a << "..." << obj1.b << "\n";
// OBJ1 0...0
abc obj2;
// Default constructor
std::cout << "OBJ2 " << obj2.a << "..." << obj2.b << "\n";
// OBJ2 0...0
obj2 = 100;
// Int constructor
// Assignment operator (0, 0) = (100, 0)
// Destructor Called
std::cout << "OBJ2 " << obj2.a << "\n";
// OBJ2 100
return 0;
// Destructor Called
// Destructor Called
你基本上已经拥有了一切,让我们来看看惊喜。
第一个惊喜:即使 obj2
稍后更改值 abc obj2;
仍会在声明时调用默认构造函数。
第二个惊喜:obj2 = 100
其实就是obj2.operator=(abc(100));
的意思,也就是:
abc(100)
构建一个临时(未命名)abc
将其分配给obj2
在继续下一条语句之前销毁临时文件
第三个惊喜:析构函数在作用域的末尾被调用,就在右括号 之前(是的,在
return
之后)。由于您使用的是system("pause")
,因此我假设您在 Windows => 尽管幸运的是,它们在您结束暂停后被调用,因此您的控制台 Windows 在它们出现的那一刻就消失了。您可以从更永久的控制台启动程序,也可以使用额外的范围:
int main ()
// your code here
system("pause");
return 0;
【讨论】:
【参考方案4】:这是因为有构造函数可以接受int
类型的参数。该临时创建的对象通过调用默认复制分配复制到obj2
。
为避免此类转换,请将构造函数标记为显式。
【讨论】:
【参考方案5】:你的析构函数被调用了 3 次,因为暂停你看不到它。
【讨论】:
代替pause
,我使用了“getchar()”,但我仍然只能看到一次析构函数调用
你会看到他们之后你按下一个键。
但是他没有看到它们,因为当他按下一个键时他的输出窗口消失了:-)
@RasmiRanjanNayak 直到 main 超出范围(或超出范围)之后才会调用析构函数。如果您的 IDE 不允许您在窗口消失之前看到输出,那么; 1) 从 shell 运行或 2) 将输出记录到文件中。
为什么人们首先要以这种方式构建项目?如果您 Debug > Start without Debugging 或按 Ctrl-F5,它会在应用程序完全终止但窗口关闭之前自动暂停。以上是关于为啥构造函数被调用两次的主要内容,如果未能解决你的问题,请参考以下文章