3

Here, when I push to the stack, why are the objects being destroyed?

#include <iostream>
#include <stack>

class One
{
private:
        int i;
public:
        One(int i) {this->i = i;}
        ~One() {std::cout << "value " << this->i << " is destroyed\n";}
};

int main()
{
        std::stack<One> stack;
        stack.push(One(1));
        stack.push(One(2));

        std::cout << "Now I'll stop\n";
}

I expected to see no output before Now I'll stop. But I get this

value 1 is destroyed
value 2 is destroyed
Now I'll stop
value 1 is destroyed
value 2 is destroyed

What should I do if I want prevent them from destroying?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Sayan
  • 99
  • 4
  • 5
    `One(1)` creates a temporary object which is *copied*. Once copied and the `push` function returns the temporary object isn't needed anumore and its life-time ends and it's destructed. – Some programmer dude Jul 14 '21 at 08:04
  • 1
    Does this answer your question? [Is std::vector copying the objects with a push\_back?](https://stackoverflow.com/questions/2275076/is-stdvector-copying-the-objects-with-a-push-back) – Ken Y-N Jul 14 '21 at 08:06
  • 3
    Seeing the upvoted answer is not represented in the tagged duplicate, I don't think this Q should be closed as duplicate – JHBonarius Jul 14 '21 at 08:11
  • Is std::vector copying the objects with a push_back? partly answers my questions. @JHBonarius I actually wanted to do something complex like storing pointers in the class. If I have one more attribute in the class `int *ip;` and modify the constructor and destructors as `One(int i) {this->i = i; this->ip = new int(i); }` `~One() {std::cout << "value " << this->i << " is destroyed\n"; free(this->ip);}`, I have a problem of double free. – Sayan Jul 14 '21 at 08:23
  • 1
    @Sayan objects created via `new` have to be released by `delete`, `free` is only of [`malloc`. `calloc`, `realloc`](https://en.cppreference.com/w/c/memory/free) – Wolf Jul 14 '21 at 08:39
  • I change the question in a detail which only shows a mistake in terminology. In C++ classes are neither created nor destroyed at runtime, whereas objects (which are managed after the rules given in classes) are. @Sayan I hope this doesn't obscure your original intent. – Wolf Jul 14 '21 at 09:17
  • @Sayan `this->ip = new int(i); [...] free(this->ip);` -> [my reaction](https://youtu.be/31g0YE61PLQ) – JHBonarius Jul 14 '21 at 14:40

3 Answers3

12

One(1) and One(2) construct two temporary objects, which are passed to push and then copied (moved) into stack. Temporaries are destroyed after the full expression immediately.

If you want to avoid constructing temporaries you can use emplace instead.

Pushes a new element on top of the stack. The element is constructed in-place, i.e. no copy or move operations are performed.

E.g.

stack.emplace(1);
stack.emplace(2);
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • The reference to [`emplace`](https://en.cppreference.com/w/cpp/container/stack/emplace) you gave shows a C++11 above C++17 of the first declaration. Is it correct at all? – Wolf Jul 14 '21 at 08:30
  • 2
    @Wolf [Return value](https://en.cppreference.com/w/cpp/container/stack/emplace#Return_value) are different since C++17. – songyuanyao Jul 14 '21 at 08:32
  • Interestingly enough, it doesn't seem to be constexpr in C++20 (or cppref needs to be updated). p0784 should apply? – JHBonarius Jul 16 '21 at 09:51
  • 1
    @JHBonarius You meant `emplace`? I check it [here](https://timsong-cpp.github.io/cppwp/stack#defn) and seems to be non-constexpr.. – songyuanyao Jul 16 '21 at 10:00
  • My mistake. I had the misconception that in C++20 all containers were made constexpr. But it seems only `std::vector` is... And although `std::stack` builds on other containers, including `std:vector`, the majority is non-constexpr. – JHBonarius Jul 16 '21 at 13:07
  • Follow up question: Why doesn't this work with vector? `#include #include #include #include class one { private: int *i; public: one(int i); ~one(); }; one::one(int i) { this->i = (int *) malloc(sizeof(int)); memcpy(this->i, &i, sizeof(int)); std::cout << "Creating " << *(this->i) << '\n'; } one::~one() { std::cout << "Destroying " << *(this->i) << '\n'; free(this->i); } int main() { std::vector two; two.emplace_back(1); two.emplace_back(2); two.emplace_back(3); }` . This gave me segmentation fault. – Sayan Jul 17 '21 at 15:33
  • @Sayan Don't use raw pointer, use smart pointers instead, or just `int i;`. If you have to, see [What is The Rule of Three?](https://stackoverflow.com/q/4172722/3309790) – songyuanyao Jul 17 '21 at 15:40
4

Let me add an illustrating detail to songyuanyao's answer which solves the actual problem.

If you add a copy constructor to class One, you will understand more easily what's going on and also how the balance between creation and destruction is maintained. (Didn't getting 4 destructions for two creations look like magic to you?)

As long as you don't define (or mention) a copy constructor, the compiler creates one for you that has a trivial implementation which does a bitwise copy of all members. So in your case, the behavior you observe may be confusing but doesn't do anything particularly bad. This of course will change as soon as you add members that involve more than just copying, for instance pointers to members that have to be destroyed somewhere...

In the following example, I added output to both constructors and also switched from assigning members in constructor body to initialiser list:

class One
{
  public:
    One(int rhs): i(rhs) {
        std::cout<< "value " << i << " was created from int\n";
    }
    One(const One& rhs): i(rhs.i) {
        std::cout<< "value " << i << " was created by copy\n";
    }
    ~One() {
        std::cout << "value " << i << " is destroyed\n";
    }

  private:
    int i;
};
Wolf
  • 9,679
  • 7
  • 62
  • 108
1

When you do this stack.push(One(1)); it creates a temporary object called rvalue of One(1) which is then copied to Stack. So After Copy, temporary objects are getting destroyed.

foragerDev
  • 1,307
  • 1
  • 9
  • 22