4

Here I a piece of C++ code.

Code 1:

#include <iostream>
#include <map>
#include <string>
#include <cstdlib>



using namespace std;

class Person {
    private:
        int year;
        Person(const Person& pers);
    public:
        Person(int y): year(y)
        { cout << "Default constructor" << endl;}

        ~Person()
        {
            cout << "Destructor " << endl;
        }

        int get_year() const
        {
            return year;
        }
};


int main()
{
    map<string, const Person&> test;
    test.insert(pair<string, const Person&>("ini_1", Person(2)));
    test.insert(pair<string, const Person&>("ini_2", Person(3)));

    cout << "over" << endl;

    map<string, const Person&>::iterator iter = test.begin();

    while(iter != test.end())
    {
        cout << iter->first << endl;
        cout << "year is equal to: " << iter->second.get_year() << endl;
        iter++;
    }

    system("pause");

    return 0;
}

Output 1:

Default constructor
Destructor
Default constructor
Destructor
over
ini_1
year is equal to: 2
Address for value: 0x6ffeb8
ini_2
year is equal to: 3
Address for value: 0x6ffed8

Code 2:

#include <iostream>
#include <map>
#include <string>
#include <cstdlib>



using namespace std;

class Person {
    private:
        int year;
        Person(const Person& pers);
    public:
        Person(int y): year(y)
        { cout << "Default constructor" << endl;}

        ~Person()
        {
            cout << "Destructor " << endl;
        }

        int get_year() const
        {
            return year;
        }
};


int main()
{
    map<string, const Person&> test;

    Person per(2);
    test.insert(pair<string, const Person&>("ini_1", per));


    cout << endl;
    cout << "over" << endl;
    cout << endl;

    map<string, const Person&>::iterator iter = test.begin();
    cout << iter->first << endl;
    cout << "year is equal to: " << iter->second.get_year() << endl;

    system("pause");

    return 0;
}

Output 2:

Default constructor

over

ini_1
year is equal to: 2

I have a couple of questions regarding these two pieces of code:

  1. For code 1 and its result:

1.1. The destructor runs for both "ini_1" and "ini_2", I think the Person class for both of them has been removed. But when I printed out the value for key "ini_1" and "ini_2", why does the member data still exist?

1.2 After the destructor run, what I think is that the Person class would not exist any more. And the value for the map is the const reference of the Person class. The reference is only an alias of Person clas, it should be removed too when the Person class's destructor run. My question is how did it happen even that the destructor run but the reference Person class still existed in the test map's value? Can you show me how does it store in the memory?

  1. For code 2 and its result:

2.1 For code 2, the destructor did not run compared to code 1. The difference for code 1 and code 2 is that code 1 has one more iteration than code 2. And form the code 1's output we can see, the memory addresses to store the value of "ini_1" and "ini_2" are different. My question is why the destructor run for code 1? Why the destructor did not run for code 2?

Thanks for your time.

wangmyde
  • 77
  • 8
  • 1
    The destructor should run in the second program - but after the `system("pause")`. – aschepler Dec 17 '19 at 15:07
  • 1
    The destructor did not run for code 2 because the object isn't out of scope until main returns, and `system("pause")` is keeping main from returning. If you look at the disassembly you'll see that the `Person` destructor is called just before main returns 0. – h0r53 Dec 17 '19 at 15:08
  • 2
    Regarding "Code 1": using an object that does not exist has undefined behaviour. Anything can happen, and reasoning about why and how is mostly futile. (Related: if I die now, my remains will be around until they are removed, and my keys will still be in my pocket. Your seeing my body or finding my keys in my pocket does not imply that I'm not dead.) – molbdnilo Dec 17 '19 at 15:09
  • 1
    In general, calling a destructor does not mean that the data of an object is actually removed from memory. It does mean that it could be marked for reallocation though, which can lead to undefined behavior. – h0r53 Dec 17 '19 at 15:15
  • 1
    Code 1: What I'm observing is the `Person` objects are created on the stack at runtime. The compiler reserves stack space for the `Person` objects, so even if they go out of scope within the `main` function, their data still exist, which is why references to that data are consistent with the original values. This could be compiler specific though, so in general this may be considered undefined behavior. – h0r53 Dec 17 '19 at 15:23
  • Hi, @molbdnilo . So is taht not correct to input an undefined Person class as the paremeter of map value? That is to say, must I do `Person per(2)` first, then `test.insert(pair("ini_1", per(2)));`? Is there a better way that I do not need to define the class fisrt? Thanks. – wangmyde Dec 17 '19 at 15:23
  • Hi, @h0r53, Is there a better way that I do not need to define the class fisrtly? Can I use the `new` operator, like `test.insert(pair("ini_1", new Person(2)));`? Will this new operation also will lead to undefined behavior? Thanks. – wangmyde Dec 17 '19 at 15:26
  • 2
    @wangmyde You need a `std::map`. A `const` reference *can* extend the lifetime of a temporary object in some cases, but this is not one of those cases. – François Andrieux Dec 17 '19 at 15:29
  • 1
    Using `new` will almost certainly cause issues, because the `Person` object will be stored on heap allocated memory. 1) Unless you call `delete` on the object, you'll have a memory leak. 2) Even if you do `delete` the object, the heap memory it allocated will be available for reuse, while your map still points to that memory. This is even worse. – h0r53 Dec 17 '19 at 15:30
  • @h0r53, thanks. Is there a way to create a map value without defining the Person class first. Does @François Andrieux way work to just do it as `test.insert(pair("ini_1", Person(2)));`? Will it lead to any problem? Thanks. – wangmyde Dec 17 '19 at 15:36
  • 1
    @wangmyde - try it out! If you remove the `const` portions from your code (along with the private copy constructor) you'll find that `test.insert()` creates a copy of `Person`. So the original object is indeed destructed, but a copy is created for the map. This seems like a safe implementation. No problems that I see. The map has its own instance of each `Person` so there shouldn't be any undefined behavior. – h0r53 Dec 17 '19 at 15:39
  • @François Andrieux, if I do `test.insert(pair("ini_1", Person(2)))`, will the copy of Person exist as long as the existing of the map? Thanks. – wangmyde Dec 17 '19 at 15:43

0 Answers0