简体   繁体   English

如果将变量设置为等于新对象,旧对象会发生什么情况?

[英]What happens to the old object if you set a variable equal to a new object?

Say we have a class X that does not have an overloaded operator=() function.假设我们有一个没有重载operator=()函数的类 X。

class X {
    int n;

    X() {n = 0;}
    X(int _n) {n = _n;}
};

int main() {
    X a;    // (1) an object gets constructed here

    // more code...

    a = X(7);    // (2) another object gets constructed here (?)

    // some more code...

    a = X(12);    // (3) yet another object constructed here (?)
    return 0;
}

Is a new object constructed at (2)?是否在 (2) 处构造了一个新对象? If it is, what happens to the old object that was constructed at (1)?如果是,那么在 (1) 处构造的旧对象会发生什么? Is it automatically destructed or deallocated (which is it)?它是自动销毁还是释放(是它)? Is it overwritten?是否被覆盖?

And what happens farther down the code at (3)?在 (3) 处的代码更远的地方会发生什么?

And most importantly, is there ever a chance of causing a memory leak by writing code like in the above?最重要的是,编写上述代码是否有可能导致内存泄漏?

At point (2) three things happen:在点 (2) 发生三件事:

  1. A temp object is constructed using the X(int _n) constructor.使用X(int _n)构造函数构造临时对象。
  2. The default assignment operator is used to copy the contents of the temporary to a .默认的赋值运算符是用来临时的内容复制到a
  3. The temporary goes out of scope and its default destructor is invoked.临时对象超出范围并调用其默认析构函数。

The same thing happens at point (3).同样的事情发生在点 (3)。

At the end of the function, the default destructor on a is invoked.在函数结束时,调用a上的默认析构函数。

What you need to understand is that there's a lot of "implicit" code generated by the compiler that you, as a novice, don't know about.您需要了解的是,编译器生成了很多“隐式”代码,作为新手,您不知道这些代码。 We'll use your code for class X as a direct example:我们将使用您的class X代码作为直接示例:

class X {
    int n;
public: //You didn't include this, but this won't work at all unless your constructors are public
    X() {n = 0;}
    X(int _n) {n = _n;}
};

Before the code gets turned into Object Code, but after your compiler gets ahold of your class definition, it transforms your class into something that looks (roughly) like this:在代码变成目标代码之前,但在您的编译器获得您的类定义之后,它会将您的类转换为(大致)如下所示的内容:

class X {
    int n;
public:
    X() {n = 0;} //Default-Constructor
    X(int _n) {n = _n;} //Other Constructor
    //GENERATED BY COMPILER
    X(X const& x) {n = x.n;} //Copy-Constructor
    X(X && x) {n = x.n;} //Move-Constructor
    X & operator=(X const& x) {n = x.n; return *this;} //Copy-Assignment
    X & operator=(X && x) {n = x.n; return *this;} //Move-Assignment
    ~X() noexcept {} //Destructor
};

The rules for when these members are automatically created are not super-obvious ( A good starting reference here ), but for now, you can trust that in this case, that's exactly what happens.何时自动创建这些成员的规则并不是很明显( 这里有一个很好的入门参考),但是现在,您可以相信在这种情况下,这正是会发生的事情。

So in your main function, let's go over what happens, with the specifics called attention to with comments:因此,在您的main函数中,让我们回顾一下发生的事情,并通过注释关注细节:

int main() {
    X a; //Default-Constructor called
    a = X(7);//Other Constructor called, then Move-Assignment operator called,
    //then Destructor called on temporary created by `X(7)`
    a = X(12); //Same as previous line

    return 0;
    //Destructor called on `a`
}

We'll add a few more lines to show most (if not all) of the various permutations of these calls:我们将添加更多行以显示这些调用的大多数(如果不是全部)各种排列:

int main() {
    X a; //Default-Constructor
    X b = a; //Copy-Constructor (uses copy-elision to avoid calling Default + copy-assign)
    X c(5); //Other Constructor
    X d{7}; //Also Other Constructor
    X e(); //Declares a function! Probably not what you intended!
    X f{}; //Default-Constructor
    X g = X(8); //Other Constructor (uses copy-elision to avoid calling Other + move-assign + Destructor)
    X h = std::move(b); //Move-Constructor (uses copy-elision to avoid calling Default + move-assign)
    b = c; //Copy-assignment
    b = std::move(d); //Move-assignment
    d = X{15}; //Other Constructor, then Move-Assignment, then Destructor on `X{15}`.
    //e = f; //Will not compile because `e` is a function declaration!
    return 0;
    //Destructor on `h`
    //Destructor on `g`
    //Destructor on `f`
    //Destructor will NOT be called on `e` because `e` was a function declaration, 
    //not an object, and thus has nothing to clean up!
    //Destructor on `d`
    //Destructor on `c`
    //Destructor on `b`
    //Destructor on `a`
}

That should cover the basics.这应该涵盖基础知识。

And most importantly, is there ever a chance of causing a memory leak by writing code like in the above?最重要的是,编写上述代码是否有可能导致内存泄漏?

As written, no.正如所写,没有。 However, suppose your class did something like this instead:然而,假设你的班级做了这样的事情:

class X {
    int * ptr;
public:
    X() {
        ptr = new int{0};
    }
};

Now, your code would leak, because every time an X was created, you'd have a pointer that never gets deleted.现在,您的代码会泄漏,因为每次创建X时,您都会有一个永远不会被删除的指针。

To solve this, you need to make sure that A) The destructor properly cleans up the pointer, and B) that your copy/move constructors/operators are correct.要解决这个问题,您需要确保 A) 析构函数正确清理了指针,以及 B) 您的复制/移动构造函数/运算符是正确的。

class X {
    int * ptr;
public:
    X() {
        ptr = new int{0};
    }
    X(int val) {
        ptr = new int{val};
    }
    X(X const& x) : X() {
        *ptr = *(x.ptr);
    }
    X(X && x) : X() {
        std::swap(ptr, x.ptr);
    }
    X & operator=(X const& x) {
        *ptr = *(x.ptr);
        return *this;
    }
    X & operator=(X && x) {
        std::swap(ptr, x.ptr);
        return *this;
    }
    ~X() noexcept {
        delete ptr;
    }
};

This code will not leak memory if used as-is in either your main function or mine.如果在您的main函数或我的函数中按原样使用,此代码不会泄漏内存。 But of course, it doesn't stop the leaks if you do something like this:但是,当然,如果您执行以下操作,它并不能阻止泄漏:

int main() {
    X * ptr = new X{};
    return 0;
    //Whelp.
}

In general, if you never need to use pointers at all, it's recommended you use something like std::unique_ptr instead, as it gives most of this stuff for free.一般来说,如果您根本不需要使用指针,建议您改用std::unique_ptr类的东西,因为它免费提供了大部分内容。

int main() {
    std::unique_ptr<X> ptr{new X{}};
    return 0;
    //Destructor called on *ptr
    //`delete` called on ptr
}

And it's a good idea in your original class, with a caveat that, unless you explicitly change it, your class won't by copyable anymore (though it'll still be movable):在您的原始类中这是一个好主意,但需要注意的是,除非您明确更改它,否则您的类将不再可复制(尽管它仍然是可移动的):

class X {
    std::unique_ptr<int> ptr;
public:
    X() {
        ptr.reset(new int{0});
    }
    X(int val) {
        ptr.reset(new int{val});
    }
    //X(X && x); //auto generated by compiler
    //X & operator=(X && x); //auto generated by compiler
    //~X() noexcept; //auto generated by compiler

    //X(X const& x); //Deleted by compiler
    //X & operator=(X const& x); //Deleted by compiler
};

We can see the changes in my previous version of main :我们可以看到我以前版本的main的变化:

int main() {
    X a; //Default-Constructor
    //X b = a; //Was Copy-Constructor, no longer compiles
    X c(5); //Other Constructor
    X d{7}; //Also Other Constructor
    X f{}; //Default-Constructor
    X g = X(8); //Other Constructor (uses copy-elision to avoid calling Other + move-assign + Destructor)
    X h = std::move(c); //Move-Constructor (uses copy-elision to avoid calling Default + move-assign)
    //b = c; //Was Copy-assignment, no longer compiles
    c = std::move(d); //Move-assignment
    d = X{15}; //Other Constructor, then Move-Assignment, then Destructor on `X{15}`.
    return 0;
    //Destructor on `h`
    //Destructor on `g`
    //Destructor on `f`
    //Destructor on `d`
    //Destructor on `c`
    //Destructor on `a`
}

If you want to use std::unique_ptr , but also want the resulting class to be copyable, you'll need to implement the copy constructor yourself using the techniques I discussed.如果您想使用std::unique_ptr ,但又希望生成的类是可复制的,则需要使用我讨论的技术自己实现复制构造函数。

And that should be about it!这应该是关于它的! Let me know if I missed anything.如果我错过了什么,请告诉我。

Is a new object constructed at (2)?是否在 (2) 处构造了一个新对象?

Yes, the temporary object X(7) .是的,临时对象X(7) But it gets destroyed right at the end of the statement.但它会在语句结束时被销毁。

If it is, what happens to the old object that was constructed at (1)?如果是,那么在 (1) 处构造的旧对象会发生什么?

Its default operator= that was generated by the compiler is called, passing in the temporary object X(7) .调用由编译器生成的默认operator= ,传入临时对象X(7)

Is it automatically destructed or deallocated (which is it)?它是自动销毁还是释放(是它)?

Not in the assignment.不在任务中。 Its deallocated when the function exits though.它在函数退出时被释放。

Is it overwritten?是否被覆盖?

Only the value of its member n changes, but that can be thought of as being overwritten.只有它的成员n的值发生了变化,但这可以认为是被覆盖了。

And what happens farther down the code at (3)?在 (3) 处的代码更远的地方会发生什么?

The same as in (2).与(2)相同。

is there ever a chance of causing a memory leak by writing code like in the above?通过编写上述代码是否有可能导致内存泄漏?

Yes, if you allocate dynamic memory or some other resource, which you're not doing here.是的,如果你分配动态内存或其他一些资源,你在这里没有做。

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

相关问题 在范围内创建新对象并退出时会发生什么 - What happens when you create new object inside a scope and exit 如果使用&#39;new&#39;来实例化一个对象而不将其赋值给变量会发生什么? - What happens if 'new' is used to instantiate an object without assigning it to variable? 与 C++ 中未命名的 object 交换后旧的会发生什么 - What happens to the old one after swapping with an unnamed object in C++ 当您多次声明同一对象/变量时会发生什么(新手) - what happens when you declare the same object/variable more than once (newbie) 如果通过 lambda function object 返回对局部变量的引用会发生什么? - What happens if you return a reference to a local variable through a lambda function object? 使用另一个(现有)对象创建新对象时会发生什么? - What happens when a new object is created using another (existing) object? 在C ++中重新分配对象变量时,原始对象会发生什么? - When object variable is reassigned in C++, what happens to the original object? 通过赋值初始化C ++对象时会发生什么? - What happens when you initialize a C++ object by assignment? 如果您从终端杀死程序,打开的ofstream对象会怎样? - What happens to an opened ofstream object if you kill the program from the terminal? 如果不移动构造函数而移动对象会怎样? - What happens if you move an object without move constructor?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM