简体   繁体   中英

Copying and destruction when returning a C++ object

I have a fairly simple piece of test code:

#include <stdio.h>

class PG
{
public:

PG(){
    m_ptr = new int;
    printf("Created PG %i\n", (int)m_ptr);
} 

~PG(){
    printf("Deleted PG %i\n", (int)m_ptr);
    delete (m_ptr);
}

PG& operator =(const PG& src)
{
    printf("Copied PG %i %i\n", (int)m_ptr, (int)src.m_ptr);
    return(*this);
}

private:
    int * m_ptr;
};

PG CreatePG()
{
    PG ret;
    return ret;
}

int main(int argc, char* argv[])
{
    PG test;
    test = CreatePG();
    printf("Ending\n");
    return 0;
}

If I compile this with GCC, VS2008 or VS2012 with full optimization and run it I get exactly what I expect:
Created PG 7837600 -created test
Created PG 7689464 -created ret
Copied PG 7837600 768946 -copied ret to test
Deleted PG 7689464 -deleted ret
Ending
Deleted PG 7837600 -deleted test

However when I compile on VS2008 or VS2012 with no optimization I get this:
Created PG 3888456 -created test
Created PG 4036144 -created ret
Deleted PG 4036144 -deleted ret. Hang on, we haven't copied it yet!
Copied PG 3888456 4036144 -We are now trying to copy deleted data
Deleted PG 4036144 -This has already been deleted. The application crashes

I can't believe that it is a bug in VS that has never been fixed but I also can't see what I am doing wrong. In my application I have an instance where this behaviour also occurs when compiling a more complicated class optimized for speed. I know it would be more efficient to use:

 PG test = CreatePG();

but I still get a similar problem though it is obviously using copy elision in this case:
Created PG 11228488
Deleted PG 11228488
Ending
Deleted PG 11228488
I am still getting the double delete.

If anyone can throw some light on this I would be very grateful.

This is because your code violates the rule of three : since you do not have a copy constructor, something important happens behind the scene for which you do not see a printout.

When you do not have a copy constructor, C++ happily defines one for you. This is usually the exact constructor that you want, except in one case: when your class manages resources explicitly. In cases like that, copying content byte-for-byte creates a false alias, when the same pointer can be deleted more than once. When you turn on optimizations, the compiler skips the calls of the copy constructor (return value optimization). With optimizations off, however, the copy constructor gets called, and then the copy of the m_ptr gets deleted, leaving the actual pointer pointing to deleted memory.

Here is how to fix this:

PG& operator =(const PG& src) {
    *m_ptr = *(other->m_ptr);
    printf("Assigned PG %x %x\n", (void*)m_ptr, (void*)src.m_ptr);
    return(*this);
}
PG(const PG& other) {
    m_ptr = new int;
    *m_ptr = *(other->m_ptr);
    printf("Copied PG %x\n", (void*)m_ptr);
}

Note: converting pointers to int is not defined; you should convert pointers to void* , and print with %x format specifier.

Where's your copy constructor? You need a copy constructor in order to return a value, and all you've got is the default copy constructor, which will result in multiple deletes of the same object if it is used (because you will end up with multiple objects containing the same pointer). As for why it does one thing when optimized, and another when not, presumably NRVO (named return value optimization) is taking place only when the code is optimized.

Here is why you are having problems.

Let's step through your code

PG test;  

Calls PG::PG() which creates a new PG with a ptr to a new int allocated on the heap at address X, Prints "Created PG X"

test = CreatePG();

First calls CreatePG()

PG ret;

Calls PG::PG() which creates a new PG with a ptr to a new int allocated on the heap at address Y, Prints "Created PG Y"

return ret;

Since the function is declared as return by value, a copy of ret is returned using the default copy constructor which does bit-wise copy. So the m_ptr of the unnamed copy that is returned is Y.

As we leave scope of CreatePG(), the destructor for local objects is called, which prints "Deleted PG Y" and then deletes Y. Then we move back to the assignment in main().

test = CreatePG();

We are now assigning the temporary unnamed PG to test using operator=. This will print "Copied PG XY" and then simply return a reference to test without actually doing anything.

At the end of the expression, the temporary goes out of scope and the destructor is called. The destructor prints "Deleted PG Y" and tries to delete Y, but Y has already been deleted so this is quite a problem.

The suggestions of writing a copy constructor that deals with pointers to the heap are good and will help you fix the problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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