为什么构造函数被调用两次

时间:2013-09-27 18:05:57

标签: c++

我不明白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

5 个答案:

答案 0 :(得分:7)

  
    

但是当我分配obj2 = 100时,编译器如何允许将整数初始化为类对象?

  

这是因为当您执行以下操作时:

obj2 = 100;

这个将首先调用abc(int x)来生成类的对象,然后调用默认的复制赋值运算符(因为没有提供用户定义)来将值100赋给现有的obj2。分配后,临时对象被破坏。

如果您不希望出现此效果,请将析构函数标记为explict以避免隐式调用。

explicit abc(int x) {
    //do something
}

答案 1 :(得分:5)

 obj2 = 100;

您定义了一个带int的构造函数。这允许从int到abc的隐式转换。这需要创建一个新对象。它不仅通过调用构造函数神奇地在现有对象中设置字段;构造函数构造新对象。

编辑:来自@Steve Jessop

的正确事件序列
  

创建一个新实例,然后将其复制分配给原始实例,然后销毁临时(不是原始实例)。复制分配确实在现有对象中神奇地设置了两个字段。

答案 2 :(得分: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
  • 构建临时(未命名)abc(100)
  • 将其分配给obj2
  • 在进入下一个声明之前销毁临时文件

第三个惊喜:在范围的最后,在结束括号}之前调用析构函数(是的,在<{em> return之后)。由于您使用的是system("pause"),我假设您使用的是Windows =&gt;虽然幸运,但是在你结束暂停之后它们会被调用,因此你的控制台Windows会在它们出现的那一刻眨眼间消失。您可以从更永久的控制台启动程序,也可以使用额外的范围:

int main () {
  {
    // your code here
  }
  system("pause"); 
  return 0;
}

答案 3 :(得分:1)

这是因为有构造函数可以采用int类型的参数。通过调用默认的副本分配,将临时创建的对象复制到obj2

要避免此类转换,请将构造函数标记为显式。

答案 4 :(得分:1)

你的析构函数被调用了3次,因暂停而无法看到它。