2

I'm trying to make a Monster generator using vectors and classes to allow the user to create and delete Monster objects as they wish. However, when I call the constructor of Monster in my code it also immediately triggers a destructor, thereby removing Monster from the the vector container.

This is the fragment of my code, which creates and destroys the Monster object:

while (p == 't')
{
    cout << "Generate (g) or remove (u) a monster?" << endl;
    cin >> p;
    while (p == 'u' && Monster::n == 0)
    {
        cout << "No monsters to remove." << endl;
        cout << "Generate (g) or remove (u) a monster?" << endl;
        cin >> p;
    }
    while (p != 'g' && p != 'u')
    {
        cout << "Error. Follow the instructions." << endl;
        cout << "Generate (g) or remove (u) a monster?" << endl;
        cin >> p;
    }
    if (p == 'g')
    {
        {
            vec.push_back(Monster());
            vec[Monster::n].print();
        }
    }
    if (p == 'u')
    {
        vec.~Monster();
    }
    cout << "Continue (t) or finish (n)?" << endl;
    cin >> p;
    while (p != 't' && p != 'n')
    {
        cout << "Error. Follow the instructions?" << endl;
        cout << "Continue (t) or finish (n)?" << endl;
        cin >> p;
    }

This is the constructor for Monster:

Monster::Monster()
{
    ostringstream buffer;
    buffer << Monster::n;
    string name = buffer.str();
    Monster::setname(name);
    Monster::settype(MonsterType(rand() % 7 + 1));
    Monster::setattack(rand() % 25);
    Monster::sethealth(rand() % 15);
    cout << endl << "Monster generated." << endl << endl;
    Monster::n++;
}

This is the destructor:

Monster::~Monster()
{
    cout << "Monster removed." << endl << endl;
    Monster::n--;
}

When I input the character 'g', it creates a Monster object as expected but then immediately destroys it, even before its stats are printed with the print() method. This means my code is not acting as expected, as it will never allow the proper creation or destruction of Monster objects.

Can someone please help me understand what's going on here?

EDIT:

Alright, I was able to somewhat solve the issue by making another constructor.

This is it:

Monster::Monster(const Monster & mon)
{
    Monster::name = mon.name;
    Monster::type = mon.type;
    Monster::attack = mon.attack;
    Monster::health = mon.health;
    cout << endl << "Monster created (cpy)." << endl << endl;
    Monster::n++;
}

I edited the point where the user decides to generate a Monster to the following:

    {
        vec.push_back(Monster());
        vector<Monster> veccpy = {};
        veccpy.push_back(Monster(vec[Monster::n-1]));
        vec.back().print();
    }

When I lauch the program, it spits out something like this:

Monster created

Monster created (cpy)

Monster removed

Monster created (cpy)

Monster created (cpy)

Monster removed

Monster's stats

Now, the odd thing is even though it uses those constructors so oddly, the program works as I intended it to. n iterates as it should, I can remove or add Monster as I intended and the Monster stats are displayed as they should.

Could someone point to what I'm doing wrong here?

Kuba Chmiel
  • 69
  • 1
  • 8
  • 3
    You have failed to follow the Rule of Three. – Kerrek SB Mar 29 '17 at 12:49
  • 1
    `vec.~Monster();` -- Do **not** do this. Explicitly calling the destructor should only be done for things such as `placement-new`. If you're doing this to fix a bug in creating or destroying instances, it is the wrong fix. – PaulMcKenzie Mar 29 '17 at 12:54
  • In addition, you failed to take into account the `Monster` copy constructor. Thus your destructor that subtracts from the monster count will be called without you knowing about it, causing it to probably give you a negative count. – PaulMcKenzie Mar 29 '17 at 12:59
  • [What is The Rule of Three?](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – Lundin Mar 29 '17 at 13:04

3 Answers3

3

In this line:

vec.push_back(Monster());

You are creating a temporary object of type Monster, which is being copied to the vec. Because it is a temporary object it will be destroyed right after the statement ends at the semicolon. It will be destroyed after it was copied to vec.

vec.~Monster();

This surely does not compile, if you wanted to clear your vector then use vec.clear()

In your code when you write Monster::setname(name); most programmers will think you are setting a static variable. I hope you don't keep name of your monster as a static variable.


Another thing you need to fix in your code is to make sure it adheres to the rule of three (as it was in comments). Your Monster::n variable is a kind of resource which is being managed by Monster class, and to correctly manage it you need to appropriately modify it in all the constructors and operators which might create/destroy Monster class instance. Without describing it with long text below is a sample code which presents which constructors/operators are bing called in your code:

http://coliru.stacked-crooked.com/a/9184f137b7a90995

struct Monster {
    Monster() { std::cout << n << ":" << __PRETTY_FUNCTION__ << "\n"; n++; }
    ~Monster() { std::cout << n << ":" << __PRETTY_FUNCTION__ << "\n"; n--; }
    Monster(const Monster&) { std::cout << n << ":" << __PRETTY_FUNCTION__ << "\n"; n++; }
    Monster& operator=(const Monster&) { std::cout << n << ":" << __PRETTY_FUNCTION__ << "\n"; n++; return *this; }        
    static int n;    
};
int Monster::n;

int main()
{
    {
    std::vector<Monster> vec;
    std::cout << "-------------- START\n";
    vec.push_back(Monster());    
    std::cout << "-------------- END\n";
    }
    std::cout << Monster::n; // should output 0
}

-------------- START
0:Monster::Monster()                  // temporary created
1:Monster::Monster(const Monster&)    // vector element is copy constructed
                                      //  from temporary here you have missed Monster::n++
2:Monster::~Monster()                 // temporary monster destroyed
-------------- END
1:Monster::~Monster()                 // this is a vector being destroyed
0                                     // this should always be 0, otherwise
                                      //  still something is missing.
marcinj
  • 48,511
  • 9
  • 79
  • 100
  • "this surely does not compile, if you wanted to clear your vector then use vec.clear()" I just want to remove the last element of the vector. Would pop_back() be ok? – Kuba Chmiel Mar 29 '17 at 12:56
  • Yes, that would be fine. Also instead of `vec[Monster::n].print();` you may do `vec.back().print();` – marcinj Mar 29 '17 at 12:59
  • @HolyBlackCat vec is of type vector - at least this is what I assume. So following `std::vector vec; vec.~Monster();` does not compile. – marcinj Mar 29 '17 at 13:20
  • @marcinj Nvm, sorry. I should've checked the `vec` type. – HolyBlackCat Mar 29 '17 at 13:21
2
vec.push_back(Monster());

This is going to create a temporary object, an instance of the Monster class, and pass it to the vector's push_back() method.

push_back() will take its parameter and copy it into the vector. Since the vector owns its object, this will involve the construction of another instance of the Monster class.

After push_back() returns the temporary object that it passed as the parameter is going to get destroyed, thus invoking your object's destructor. And that's why you see a destructor getting invoked here.

Note that when adding objects to a vector, the vector may choose to reallocate its contents, at any time. This will involve, essentially, copy-constructing new instances of the class in the vector, then destroying the old ones, as well.

Lessons learned

Spend some time reading your C++ book and learn how smart pointers work, and put smart pointers in your vector, rather than object instances, in order to avoid unwanted side effects due to temporaries.

You could also implement move constructors as an alternative solution, but using smart pointers will probably be easier.

A second alternative is to emplace_back(), rather than push_back() new instances. However that won't solve your problem completely. See "reallocation", above.

Additionally, it wouldn't hurt to make your class Rule of 3 compliant either. The real underlying reason you asked this question is likely because it's currently not.

Community
  • 1
  • 1
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
2

What you are seeing is expected and it's happening because you have defied the vec as std::vector<Monster>. What does it means that you are declaring the vector that would contains the objects of Monster class.

Now the way vector works is that they need to keep all those Monster class objects alive unless you remove them from the vector. Which means, the scope of those Monstors object should be equivalent till the Object remains the vector.

Having said that, now if you look at this line of code vec.push_back(Monster());, the new Monster object hat you are declaring will have the scope till the scope of braces with in the if, and as soon it it will be out of scope, it will not be accessible. Which is why, when the push_back gets executed, it copies the given Monster object (by executing its default copy constructor, or custom one if any), and create a new object for the vector.

If you don't want to the object to be copied, what you do is as follow:

Declare vector as std::vector<std::unique_ptr<Monster>> and call push_back as .push_back(make_unique<Monster>()).

Here instead of declaring the Monster on the local stack, you are declaring it on the Heap and using the smart pointer to manage the life of the object on the heap.

Mudi
  • 131
  • 7