11

When a variable is reassigned, the destructor is not called:

Object foo = Object(a,b);
foo = Object(c,d);

so, the destructor will only be called at the end of the scope for Object(c,d), which can obviously cause problems. Now, in this specific case it doesn't bother me too much: it is enough to declare 2 different objects:

Object foo1 = Object(a,b);
Object foo2 = Object(c,d);

In this way the destructor of both objects will be called at the end.

However, there is a case when I necessarily need to reassign a variable, i.e. in an object constructor such as:

SuperObject(Point point1, Point point2) : delay_object_(DelayObject(0)) {
  double distance = distance(point1, point2);
  double delay = distance / speed;

  delay_object_ = DelayObject(delay);
}

In fact the DelayObject parameter is not easy to calculate (in this example I also omitted a few other passages), and I want to avoid doing it in the initialisation list.

I thought I can force the deletion by putting the object in the heap and explicitly calling the destructor:

SuperObject(Point point1, Point point2) : p_delay_object_(new DelayObject(0)) {
  double distance = distance(point1, point2);
  double delay = distance / speed;

  delete p_delay_object_;
  p_delay_object_ = new DelayObject(delay);
}

but this really looks ugly to me, as I prefer to use dynamic allocation only when strictly necessary. Am I missing something?

Cheers!

Enzo
  • 964
  • 1
  • 9
  • 20
  • is there a reason you didn't type `Object foo(a,b)`? And then later change foo's values to c and d? Why do you feel the right way to proceed is to create and then copy around entire objects? I don't think you're thinking with stack semantics. – Kate Gregory Jul 04 '11 at 19:47
  • Thanks Kate. No reason for that: its been a long time since I last worked with c++. – Enzo Jul 04 '11 at 20:24

4 Answers4

10

"the destructor will only be called at the end of the scope for Object(c,d)"

False. Object(c,d) is a temporary, and its destructor is called at the end of the full-expression which creates it. In this case, that's the semi-colon at the end of foo = Object(c,d);. The destructor of foo is called at end-of-scope.

The assignment operator of Object should free or re-use resources already held by foo, and copy resources held by the temporary. Not necessarily in that order (see copy-and-swap).

Edit: in response to comment.

Object foo = Object(a,b);

Either

  1. A temporary is constructed using whatever two-argument constructor matches (a,b).
  2. foo is constructed using the copy-constructor, passing the temporary as argument.
  3. The temporary is destroyed.

Or

  1. foo is constructed using whatever two-argument constructor matches (a,b).

The implementation is free to do either - this is permitted by "copy constructor elision".

foo = Object(c,d);
  1. A temporary is constructed using whatever two-argument constructor matches (c,d).
  2. The assignment operator for class Object is called on foo, passing the temporary as argument.
  3. The temporary is destroyed.

Some time later, at the end of the scope, foo is destroyed.

In C++0x, move assignment would come into play if it exists for the class.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Thanks Steve, and to everybody. I didn't hope to get so many professional answers.Could you please explain exactly what happens in a step-by-step fashion? – Enzo Jul 04 '11 at 20:15
  • My guess is: (1) `Object foo = Object(a,b);` // Object(a,b) is created, foo is created and Object(a,b) is copied into foo. (2) `foo = Object(c,d);` // Object(c,d) is created and is copied into foo (3) Object(a,b), Object(c,d), and foo are destructed in that order. – Enzo Jul 04 '11 at 20:28
  • Am I understanding correctly, that given during foo = Object(c,d); the destructor is never explicitly called, it's the responsibility of the copy/move assignment operators to clean up memory of the copied-to or moved-to object? – Dragonsheep Aug 15 '22 at 23:03
  • @Dragonsheep: Not sure I understand the question. The copied-to (or moved-to) object is `foo`, which is destructed at end of scope. The copied-from or moved-from object is the temporary, which is still destructed even though a move operator would have left no resources sitting with that temporary. The destructor therefore most likely wouldn't have any work to do, and perhaps once everything is inlined the compiler will see that it doesn't need to emit any code to do that nominal destruction (e.g. if the compiler can track that a pointer was nulled, it can omit the call to delete it). – Steve Jessop Nov 23 '22 at 12:13
  • You're right, though, that the destructor is not *explicitly* called. None of this destruction appears explicitly in the user's code, it's all stuff guaranteed by the standard and implemented by the compiler. – Steve Jessop Nov 23 '22 at 12:15
8

You should overload the assignment operator, which would, conceptually, copy construct the existing object and destroy the old 'this'.

class Object {
  ...
  Object& operator= (const Object& other) {
     if (this != &other) {
       // copy 'other' into 'this'.
     }
     return *this;
  }
  ...
};

then the foo = Object(c,d); line should do what you expect.

(Also as @Steve Jessop mentioned, the temporary object Object(c,d) will also be destructed after before the end of the scope.)

See What is The Rule of Three?.

Community
  • 1
  • 1
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • @Steve: Thanks. Abstracted out the comment to just say "copy 'other' into 'this'" :). BTW, for those who're interesting in copy-and-swap: http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom. – kennytm Jul 04 '11 at 19:52
  • Just say no to this, it is going to kill you when dynamic type of object is not equal to its static type. Also, how are you going to handle operator= in classes derived from Object? Use create temporary and swap idiom. – Tomek Jul 04 '11 at 19:54
  • Perhaps it's worth mentioning that whenever you find that you **have** to write your own destructor, and hence provide copy and assignment constructors, you should think twice. Oftentimes there's a better way using modern library tools to avoid this entirely. As Stephan T Lavavej puts it, "don't write your own copy constructor, that's evil"... – Kerrek SB Jul 04 '11 at 20:01
  • @Tomek: I don't understand your point. Create-temporary-and-swap and sub-typing are two different problem. You need to override `operator=` again in the derived class with or without that idiom. You can't change an `Object` on stack to a `Derived` by assignment. – kennytm Jul 04 '11 at 20:14
  • Thanks everybody! I was trying to follow Google c++ style guide, which suggests not to overload operators; especially the copy constructor. @Tomek, what does it mean to "Use create temporary and swap idiom"? – Enzo Jul 04 '11 at 20:19
  • 1
    @Enzo: It's always preferable to design your class with intelligent data members so that you don't *have* to provide a custom copy constructor. But you have to know how to design your class (and it's probably not always possible). – Kerrek SB Jul 04 '11 at 20:25
  • @KennyTM if you have operator= which destructs and reconstructs object in base class, it will recreate base class object and not the derived one when assigning to derived class object. So you cannot reuse base::operator= in derived::operator=. This means derived::operator= has to duplicate all functionality of base::operator= which means no private data in base and breaks encapsulation. Just say no to this. – Tomek Jul 05 '11 at 07:17
  • If I remember correctly the answer has changed so this may not be the best place for the following links but for the reading: http://www.gotw.ca/gotw/023.htm explains why it is bad to delete and copy construct in operator= and http://www.gotw.ca/gotw/022.htm has more on object lifetime. – Tomek Jul 05 '11 at 07:26
1

Yes, you're forgetting that you can overload the assignment operator. The one provided by default by the compiler simply copies blindly all the fields of the objects, but you can provide your own that will do whatever you want, including disposing the resources of the object target of the assignment.

Notice that it's easy to get wrong an assignment operator (forgetting about corner cases, exception safety, ...), I advise you to read at least this part of our operator overloading FAQ, which in turn redirects to the explanation of the common copy and swap idiom, which is almost always the only sensible way to implement the assignment operator.


Edit

See in addition @Steve Jessop's answer, that considers another fundamental misunderstanding.

In all cases in which you write

SomeClass sc = SomeClass(parameters);

you are initializing a new object: you're creating a temporary (SomeClass(parameters)) and then initializing sc with a copy of it (via the copy constructor); this is both unnecessary and inefficient, the syntax to create an object on the stack in C++ is just

SomeClass sc(parameters);
Community
  • 1
  • 1
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • Copy ctor elision deals with the inefficiency in an even halfway-decent compiler, and the two forms become equivalent. I slightly prefer the version with the `=`, because it never falls over the most vexing parse, but it's a matter of style. Of course if the profiler says that copy ctor elision has failed then that would have to be addressed. – Steve Jessop Jul 04 '11 at 19:53
  • 1
    It still looks like Java/C# to me, and it feels wrong. I also don't like it because I never remember if conceptually it's a temporary+copy, or some special case of "normal" construction alike the `=` to call 1-parameter constructors. – Matteo Italia Jul 04 '11 at 19:55
  • C++ was there first, if Java/C# look like it then that doesn't bother me ;-) But as I say, style issue, provided that the class *has* a copy ctor we should write whatever we like the look of. – Steve Jessop Jul 04 '11 at 19:57
  • Who's addicted to Steve Jessop's answer? ;-) – Kerrek SB Jul 04 '11 at 19:58
  • Oh yeah, and it occurs to me that if the class name is long, then I'll take my chances with the most vexing parse rather than write `std::vector vec = std::vector(10);`. And yes, I do then fall over the dreaded parse when I'm trying to initialize that vector from a pair of `istream_iterator`. – Steve Jessop Jul 04 '11 at 20:04
0

Delegate calculation of DelayObject parameters to private (possibly static) methods and use them when constructing DelayObject in initializer list.

Tomek
  • 4,554
  • 1
  • 19
  • 19