1

When I have a function, let's call it foo(). In foo() I create an object called obj1 that is a type of ClassA. Also, I copy a reference of the object to std::queue<ClassA&> qu.

My question is: Is the object obj1 destroyed before foo() returns?

Code example:

class ClassA {...};

std::queue<ClassA&> qu;

void foo()
{
    ClassA obj1;
    qu.push_back(obj1);
}

int main()
{
    foo();
    return 0;
}
Matan
  • 169
  • 1
  • 10
  • 3
    Paste the full example please. It depends. – pptaszni Jul 28 '21 at 14:08
  • 2
    @pptaszni: A [mcve], that is. – Fred Larson Jul 28 '21 at 14:09
  • 5
    If it's an automatic scope local, then yes (and the reference is now dangling and unusable unless the queue also went out of scope). Please show actual code though, because the answer is different if you're using `new`. – Useless Jul 28 '21 at 14:09
  • 1
    [References are not copyable](https://stackoverflow.com/a/10455591/1460794). – wally Jul 28 '21 at 14:11
  • I added a code example, for your request. – Matan Jul 28 '21 at 14:12
  • 1
    This will not compile, but if it did obj1 would be destroyed when it goes out of scope. In this case it will go out of scope when the function returns. – wally Jul 28 '21 at 14:18
  • And if I copy the object itself? Not the reference? – Matan Jul 28 '21 at 14:18
  • 1
    Have you tried compiling your example, or any code with `std::queue qu;`? Hint: It fails. Try rephrasing your question in terms of e.g. [`std::queue< std::reference_wrapper< int > >`](https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper). – DevSolar Jul 28 '21 at 14:19
  • If you copy the object, your queue contains a *copy* of `obj1`, and it doesn't matter what happens to `obj1` (which, of course, will be destroyed). – DevSolar Jul 28 '21 at 14:21
  • Also, please **do** read the link about [mcve]. Yours isn't, and it is somewhat important to phrase questions with *reproducable* examples, not snippets. – DevSolar Jul 28 '21 at 14:23
  • The answer kind-of depends on the definition of `classA`. If it has a trivial destructor, then it's lifetime ends when its storage is released (or reused). – Daniel Langr Jul 28 '21 at 14:25

2 Answers2

6

You may not use references as element of a queue. Nor can you use references as an element of any other standard container. As such, the shown program is illformed.

That said, obj1 is an automatic variable, and therefore it is destroyed exactly when the scope ends which in this case is when the function returns.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Out of curiosity — if `ClassA` has trivial destructor, is `obj` destroyed as well (its destructor is called), or is its lifetime ended just by releasing its storage? – Daniel Langr Jul 28 '21 at 14:35
  • @Daniel: The destructor is called. – Thomas Wilde Jul 28 '21 at 14:38
  • @ThomasWilde Could you, please, point to the part(s) of the C++ standard which says so? – Daniel Langr Jul 28 '21 at 14:41
  • @DanielLangr First [this](https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization) then [this](https://timsong-cpp.github.io/cppwp/basic.life#1) – NathanOliver Jul 28 '21 at 14:44
  • @Daniel: https://en.cppreference.com/w/cpp/language/destructor – Thomas Wilde Jul 28 '21 at 14:46
  • @ThomasWilde Yes, destructor may end lifetime of an object, but it's not the only way. For trivially-destructible class, there are other ways as well. BTW, according to [this answer](https://stackoverflow.com/a/61067599/580083), **since C++17, trivial destructor does not end object lifetime** ([direct link](https://timsong-cpp.github.io/cppwp/n4659/basic#life-1)). – Daniel Langr Jul 28 '21 at 14:48
  • @NathanOliver I don't see there the answer to my question. However, it is interesting that the part in the standard you linked changed between C++17 and C++20, namely the option to end object lifetime by calling trivial destructor was added. – Daniel Langr Jul 28 '21 at 14:53
  • @Daniel: You asked if the destructor is called when the lifetime of an object ends. According to the reference - yes it is. But calling the destructor does not necessarily end the lifetime of an object. Those are two different things. --- Maybe I understood your question wrong. If that is the case please excuse, I do not want to argue here. – Thomas Wilde Jul 28 '21 at 15:05
  • @DanielLangr `if ClassA has trivial destructor, is obj destroyed as well (its destructor is called), or is its lifetime ended just by releasing its storage?` Both. The lifetime has ended, and the object is destroyed. Of course, calling a trivial destructor has no observable effects, so practical implementations simply do nothing instead of calling something (as per "as if" rule). – eerorika Jul 28 '21 at 15:18
1

The obj1 is created on the stack. As soon as a variable on the stack gets out of scope, it is destroyed. obj1 gets out of scope when foo() comes to its end. Therefore obj1 gets destroyed, and its destructor is called.

Furthermore, you cannot store references in an STL container like a queue. You could use pointers or std::reference_wrapper instead.

Have a look at this example:

#include <functional>
#include <iostream>
#include <queue>

using namespace std;
// ---------------------------------------------------------- //
struct ClassA {
  ClassA(string name)
    : name_(name) {
    cout << "create " << name << endl;
  }

  ~ClassA() { cout << "destroy " << name_ << endl; }

  int    x     = 42;
  string name_ = "";
};

// ---------------------------------------------------------- //
void
foo1() {
  cout << "start foo1()" << endl;
  ClassA obj("foo1_object");

  queue<ClassA*> foo1_queue;
  foo1_queue.push(&obj);
  cout << "end foo1()" << endl;
}

// ---------------------------------------------------------- //
void
foo2(queue<ClassA*>& queue) {
  cout << "start foo2()" << endl;
  ClassA obj("foo2_object");
  queue.push(&obj);
  cout << "end foo2()" << endl;
}

// ---------------------------------------------------------- //
void
foo3(queue<reference_wrapper<ClassA>>& queue) {
  cout << "start foo3()" << endl;
  ClassA obj("foo3_object");
  queue.push(obj);
  cout << "end foo3()" << endl;
}

// ---------------------------------------------------------- //
int
main(int argc, char const* argv[]) {
  cout << "main before foo1()" << endl;
  foo1();
  cout << "main after foo1()" << endl;
  cout << endl;
  // ---
  cout << "main before foo2()" << endl;
  queue<ClassA*> main_queue1;
  foo2(main_queue1);
  cout << "main after foo2()" << endl;
  cout << endl;

  // ---
  cout << "main before foo3()" << endl;
  queue<reference_wrapper<ClassA>> main_queue2;
  foo3(main_queue2);
  cout << "main after foo3()" << endl;
  cout << endl;
}

This gives the following output:

main before foo1()
start foo1()
create foo1_object
end foo1()
destroy foo1_object
main after foo1()

main before foo2()
start foo2()
create foo2_object
end foo2()
destroy foo2_object
main after foo2()

main before foo3()
start foo3()
create foo3_object
end foo3()
destroy foo3_object
main after foo3()
Thomas Wilde
  • 801
  • 7
  • 15