Search code examples
c++constructordestructorinstantiationraii

C++ object destroyed more than once


When writing some code that loads a (part of) a datastructure into graphics memory, I was surprised by this unexpected behaviour.

I've simplified the code to the following:

#include <iostream>
#include <vector>
using namespace std;

struct Data{
    int data;
    Data(int x){
        data = x;
        cout<<"+ Data"<<endl;
    }
    ~Data(){cout<<"- Data"<<endl;}
};

struct Handle{
    // imagine some handles to video memory here
    Handle(Data d){
        // (upload data to video memory)
        cout<<"+ Handle"<<endl;
    }
    ~Handle(){
        // (deallocate the data from video memory)
        cout<<"- Handle"<<endl;
    }
};

int main(){
    vector<Handle> container;
    cout<<"start"<<endl;
    for(int i=0; i<4; i++){
        container.emplace_back(Data(i));
        cout<<endl;
    }
    cout<<"end"<<endl;
}

what I expected:

start
+ Data
+ Handle
- Data

+ Data
+ Handle
- Data

+ Data
+ Handle
- Data

+ Data
+ Handle
- Data

end
-Handle
-Handle
-Handle
-Handle

what I got:

start
+ Data
+ Handle
- Data
- Data

+ Data
+ Handle
- Data
- Handle
- Data

+ Data
+ Handle
- Data
- Handle
- Handle
- Data

+ Data
+ Handle
- Data
- Data

end
- Handle
- Handle
- Handle
- Handle

Can anyone explain this behaviour?


Solution

  • This modification of your code may help you better see what's happening.

    #include <iostream>
    #include <vector>
    using namespace std;
    
    struct Data{
        int data;
        static int allcount;
        int count;
        Data(int x) : count(allcount) {
            data = x;
            allcount++;
            cout<<"+ Data "<< count <<endl;
        }
        Data(const Data &d) : count(allcount) {
            allcount++;
            cout<<"+ Data(copy of " << d.count << ") "<< count <<endl;
        }
        ~Data(){cout<<"- Data "<< count <<endl;}
    };
    
    struct Handle{
        static int allcount;
        int count;
        // imagine some handles to video memory here
        Handle(Data d) : count(allcount) {
            allcount++;
            // (upload data to video memory)
            cout<<"+ Handle " << count << endl;
        }
        Handle(const Handle &h) : count(allcount) {
            allcount++;
            cout<<"+ Handle(copy of " << h.count << ") "<< count <<endl;
        }
        ~Handle(){
            // (deallocate the data from video memory)
            cout<<"- Handle " << count <<endl;
        }
    };
    
    int Handle::allcount=0;
    int Data::allcount=0;
    
    int main(){
        vector<Handle> container;
        cout<<"start"<<endl;
        for(int i=0; i<4; i++){
            container.emplace_back(Data(i));
            cout<<endl;
        }
        cout<<"end"<<endl;
    }
    

    What it does is to keep track of each allocation and deallocation with an integer to uniquely identify each. Copy constructors are also explicitly added. When I run this on my system I get the following:

    start
    + Data 0
    + Data(copy of 0) 1
    + Handle 0
    - Data 1
    - Data 0
    
    + Data 2
    + Data(copy of 2) 3
    + Handle 1
    - Data 3
    + Handle(copy of 0) 2
    - Handle 0
    - Data 2
    
    + Data 4
    + Data(copy of 4) 5
    + Handle 3
    - Data 5
    + Handle(copy of 2) 4
    + Handle(copy of 1) 5
    - Handle 2
    - Handle 1
    - Data 4
    
    + Data 6
    + Data(copy of 6) 7
    + Handle 6
    - Data 7
    - Data 6
    
    end
    - Handle 4
    - Handle 5
    - Handle 3
    - Handle 6
    

    As you can see, the Handle is copied multiple times. This is because they need to be copied when the container they're in is resized. If you add the line

    container.reserve(4);
    

    just before the line that prints "start", I think you'll see what you expected to see in the first place. That is, no Handle copies.