【问题标题】: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

【问题讨论】:

    标签: c++


    【解决方案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,它会在应用程序完全终止但窗口关闭之前自动暂停。
          猜你喜欢
          • 2013-12-11
          • 1970-01-01
          • 1970-01-01
          • 2020-06-29
          • 1970-01-01
          • 2021-11-06
          • 1970-01-01
          相关资源
          最近更新 更多