为啥构造函数被调用两次

Posted

技术标签:

【中文标题】为啥构造函数被调用两次【英文标题】:Why constructor is being called twice为什么构造函数被调用两次 【发布时间】:2013-09-27 18:05:57 【问题描述】:

不明白怎么constructors work

我在这里声明了一个对象obj2。调用构造函数abc(),完全没问题。

但是当我分配时

obj2 =  100 

为什么编译器允许将整数初始化为类对象?如果它完全允许,那么它如何销毁对象以及它如何调用另一个参数化构造函数。

现在我还有一个问题,为什么 destructor 只被调用一次,因为有 two 对象?

我还有一个疑问,编译器是 not doing anythingdefault 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,它会在应用程序完全终止但窗口关闭之前自动暂停。

以上是关于为啥构造函数被调用两次的主要内容,如果未能解决你的问题,请参考以下文章

为啥自动对象的析构函数被调用两次?

tomcat服务器启动,Filter 的构造函数被调用了两次,doFilter函数调用了一次,

为啥C++里面,析构函数会被调用两次

Angular 组件构造函数被调用两次

为啥我的构造函数没有被调用?

C++:调用无参数的构造函数为啥不加括号