简体   繁体   English

为什么在构造函数和析构函数被调用时被调用? 他们实际上在做什么? C ++

[英]Why are constructors and destructors called when they are? What are they actually doing? C++

I'm sort of new to the technical reasons for constructors and destructors. 对于构造函数和析构函数的技术原因,我有些陌生。 I wrote a program with functions that display an objects starting state, end state, memory location, value, and when the constructors and destructors are called. 我编写了一个程序,该程序具有显示对象的开始状态,结束状态,内存位置,值以及何时调用构造函数和析构函数的函数。

I'm having trouble understanding why they are called when they are and what they're actually doing. 我无法理解为什么他们在何时被叫以及他们实际上在做什么。 I'll post the results of a test run and the code I'm using - as well as what I know lol. 我将发布测试运行的结果和我正在使用的代码-以及我所知道的大声笑。


RESULTS: 结果:

Constructor called on 0x7fffc053f070

Initial state: 
Location: 0x7fffc053f070
  Value: 0

--- FOO ----
Location: 0x7fffc053f070
  Value: 1

--- BAR ----
Location: 0x7fffc053f080
  Value: 2

Destructor called on 0x7fffc053f080

--- BAZ ----
Location: 0x7fffc053f070
  Value: 2


Final state: 
Location: 0x7fffc053f070
  Value: 2

Destructor called on 0x7fffc053f070


CODE: 码:

#include <iostream>
#include <vector>

using namespace std;

//Short memory addresses are on the heap
//Long memory addresses are on the stack

class A {

public:

A(){
    m_iValue = 0;
    cout << "Constructor called on " << this << endl;
}

/*      A(const A & a){
            m_iValue = a.m_iValue;
            cout << "Copy constructor called on " << this << endl;
    }
*/
void increment(){
    m_iValue++;
}

void display(){
    cout << "Location: " << this << endl;
    cout << "  Value: " <<m_iValue << endl;
    cout << endl;
}

virtual ~A(){
    cout << "Destructor called on " << this << endl;
}

private:
    int m_iValue;
};

void foo(A & a){
    a.increment();
    a.display();
}

void bar(A a){
    a.increment();
    a.display();
}

void baz(A * a){
    a->increment();
    a->display();
}

void blah(vector<A*> vA){
    vA.back()->display();
    delete vA.back();
    vA.pop_back();
}

int main(int argc, char * argv[]){

    A a;

    cout << "Initial state: " << endl;
    a.display();

    cout << endl;

    foo(a);
    bar(a);
    baz(&a);
    cout << endl;
    cout << "Final state: " << endl;
    a.display();

    return 0;
}

What I believe is happening: 我相信正在发生的事情:

So, the constructor is getting called once and the destructor is getting called twice. 因此,构造函数被调用一次,析构函数被调用两次。 The constructor is called when the object is created in main. 在main中创建对象时调用构造函数。 In foo(), m_iVariable is passed by reference, and the function increments m_iValue for the object at that location in memory. 在foo()中,m_iVariable通过引用传递,并且函数在内存中该位置的对象的m_iValue递增。 So the program displays the value as 1 (incremented from 0.) 因此程序将值显示为1(从0开始递增。)

This is where I get confused.. The third location is different from the first two locations. 这是我感到困惑的地方。第三个位置与前两个位置不同。 The object is getting passed in directly to bar(). 该对象将直接传递给bar()。 I don't understand how the location can be different without calling the constructor or why the destructor is called after it increments, making the value 2. 我不明白在不调用构造函数的情况下位置可能会有所不同的原因,或者为什么在析构函数递增后调用析构函数,使值变为2。

But baz also increments the value. 但是baz也会增加该值。 So that means bar didn't actually do anything? 这意味着bar实际上没有执行任何操作吗? I still don't get how bar displays a new memory location and destructs, but never constructs. 我仍然不知道bar如何显示新的内存位置并破坏,但从未构造。


Sorry for all the text, but anything will help. 抱歉,所有文字都可以,对您有帮助。 Thanks! 谢谢!

Oh and the commented out code, and the function blah were used for other things and aren't relative for this question. 哦,注释掉的代码以及函数等等被用于其他用途,并且与该问题无关。

When you pass by value to bar() , you are creating a new object, local to that function. 当按值传递给bar() ,您将创建该函数本地的新对象。 That object is destroyed when bar() returns. bar()返回时,该对象将被销毁。 It is initialised by the copy constructor A(A const &) , which was implicitly generated since you don't declare one yourself. 它是由副本构造函数A(A const &)初始化的,因为您不需要自己声明一个,它是隐式生成的。 If you uncomment the copy constructor, then you will see it happening. 如果取消注释副本构造函数,则将看到它的发生。

In general, you have to be careful when allowing objects with a non-trivial destructor to be copied. 通常,在复制具有非平凡析构函数的对象时必须小心。 Such classes typically manage resources which are released in the destructor, and you must take care that copies don't try to manage the same resource. 这些类通常管理在析构函数中释放的资源,并且您必须注意副本不要尝试管理相同的资源。 Always remember the Rule of Three when making classes like that. 进行此类学习时,请始终牢记三原则

In very general terms, a constructor is called when you either initialize an object implicitly by creating it on the stack as you do in your main routine, or when you explicitly allocate and initialize it using new . 一般来说,当您像在main例程中那样通过在堆栈上创建对象来隐式初始化对象时,或者使用new显式分配和初始化对象时,都会调用构造函数。 It's important to note that passing arguments to methods by value has the effect of creating a copy of an object using the copy constructor which is a type of initialization. 重要的是要注意, 按值将参数传递给方法具有使用复制构造函数 (一种初始化类型)创建对象副本的效果。

The destructor is called when an object allocated on the stack falls out of scope, or when a dynamically allocated object is released with delete . 当在堆栈上分配的对象超出范围时,或者使用delete释放动态分配的对象时,将调用析构函数。

You're seeing two destructor calls here because one of your methods is pass by value, and a copy of it is created, and, later when the method is complete, destroyed. 您在这里看到两个析构函数调用,因为其中一个方法是按值传递的,并且创建了它的一个副本,然后在该方法完成时将其销毁。 The memory address of these two objects is different. 这两个对象的内存地址不同。

If you're passing by value, any modifications made to the copy will not be reflected in the original. 如果按值传递,则对副本所做的任何修改都不会反映在原件中。 This is why in a lot of C++ applications, methods pass things either by reference , like foo(A& a) to allow modifications, or by const reference to make it clear no changes are allowed like foo(const A& a) . 这就是为什么在许多C ++应用程序中,方法要么通过引用 (例如foo(A& a)进行更改以允许修改,要么通过const引用foo(const A& a)例如foo(const A& a)更改以明确表示不允许更改。 Pointers are sometimes used for the same purposes. 指针有时用于相同的目的。

The reason you're having problems here is because you've not aware of the Rule of Three with regards to destructors, copy constructors, and copy assignment operators. 您在这里遇到问题的原因是因为您不了解关于析构函数,复制构造函数和复制赋值运算符的三元规则

First, the reason that your output shows different number of constructor and destructor calls, is that it doesn't display all such calls. 首先,您的输出显示不同数量的构造函数和析构函数调用的原因是,它不会显示所有此类调用。 In your case it's a failure of your attempt to instrument the code, not considering all constructors. 在您的情况下,尝试代码而不是考虑所有构造函数都是失败的。 In general it can also be caused by constructors throwing exceptions, because it's only the number of successful constructor cakks that matches the number of destructor calls, and it can be caused by objects not being destroyed (eg dynamically allocated and not deleted). 通常,它也可能是由构造函数抛出异常引起的,因为它仅是成功的构造函数cakk的数量与析构函数调用的数量相匹配的,并且它可能是由对象未被破坏(例如,动态分配且未删除)引起的。


Ignoring (ab)use of very low level features of the language, the C++ rules are designed to ensure that 忽略(滥用)该语言的非常低级的功能,C ++规则旨在确保

  • For each object of type T, there is exactly one T constructor call, which happens before any other call. 对于每个T类型的对象,仅存在一个T构造函数调用,该调用在任何其他调用之前发生。

  • For each constructor call that succeeds , there is a corresponding destructor call (if the object is ever destroyed). 对于每个成功的构造函数调用,都有一个对应的析构函数调用(如果对象曾被销毁)。

These rules hold recursively in an object hierarhcy. 这些规则递归地保存在对象层次结构中。 An object with sub-objects (data members) is an example of such a hierarchy. 具有子对象(数据成员)的对象就是这种层次结构的一个示例。 Instead of being a data member, a sub-object can be a base class sub-object. 子对象可以是基类子对象,而不是数据成员。

The constructor's responsibility is technically to transform a piece of raw memory into a typed and meaningful object (constrast this with a copy assignment operator, which replaces an existing value with a new one, possibly deallocating earlier allocated memory). 从技术上讲,构造函数的责任是将一段原始内存转换成一个有类型的有意义的对象(用一个拷贝赋值运算符对此进行重构,该赋值运算符用一个新的值替换现有值,可能会取消分配先前分配的内存)。 At the design level the constructor's main responsibility is to establish the class invariant , whatever you can assume about the state of the object between calls to public methods. 在设计级别,构造函数的主要职责是建立类不变式 ,无论您对公共方法的调用之间对象的状态如何,都可以假设。 The destructor's job is to clean up, eg to free resources. 破坏者的工作是清理,例如释放资源。

Construction failure is, from the language point of view, when a constructor throws an exception. 从语言的角度来看,构造失败是指构造函数抛出异常。

A new -expression provides a transactional-like very strong coupling between memory allocation and construction. new表达式在内存分配和构造之间提供了类似事务的非常强的耦合。 It allows you to provide two set of arguments: a first set of arguments for the allocation function, and a second set of arguments for the constructor. 它允许您提供两组参数:分配函数的第一组参数,以及构造函数的第二组参数。 Except for the case noted below, if the constructor fails then the memory is automatically deallocated and the exception is propagated. 除以下情况外,如果构造函数失败,则将自动释放内存并传播异常。 Ie in general either both succeed, or any side-effects are undone and the calling code informed of the failure. 也就是说,通常要么成功,要么撤消任何副作用,并向调用代码通知失败。

The single exception where the cleanup is not done, is where you have defined a custom allocation function, a so called "placement new" operator, and failed to provide a corresponding deallocation function. 未完成清理的唯一例外是您定义了一个自定义分配函数(一个所谓的“ placement new”运算符),但未能提供相应的释放函数。 I do not know the reason why the custom deallocation function is needed, and indeed, this is the only circumstance where it's implicitly called. 我不知道为什么需要自定义释放函数的原因,实际上,这是唯一被隐式调用的情况。

In your method bar(A a) , it is the copy constructor A(const A& a) that is called. 在方法bar(A a) ,调用的是复制构造函数A(const A& a) If you have not declare it, it is implicitely created by the compiler and calls the copy constructor for each member. 如果尚未声明,则由编译器隐式创建,并为每个成员调用复制构造函数。

Just add this method: 只需添加此方法:

A(const A& a){
  m_iValue = a.value;
  cout << "Copy constructor called on " << this << endl;
}

Its always fun to just simply put print statements in your constructor and destructor to see them operate on run time. 只需将print语句放入构造函数和析构函数中以查看它们在运行时运行总是很有趣的。 A lot of good answers above so I wont say anything, but doing this helped me early on in my programming career. 上面有很多好的答案,所以我什么也没说,但是这样做对我的编程生涯很​​早就有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM