Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Why c++ executes destructors of moved objects?

680 views
Skip to first unread message

Melzzzzz

unread,
Aug 9, 2016, 5:29:37 PM8/9/16
to
Given following program,

#include <cstdio>
#include <utility>

class A{
public:
A() {
printf("constructor\n");
}
A(A && moved) {
printf("move constructor\n");
}
~A(){
printf("destructor\n");
}
};

int main() {
A a;
A b = std::move(a);
}

I would expect that destructor of `a` wouldn't be called but:
[bmaxa@maxa-pc examples]$ g++ moved.cpp
[bmaxa@maxa-pc examples]$ ./a.out
constructor
move constructor
destructor
destructor
[bmaxa@maxa-pc examples]$

Is this defect, and what is point of moving around, if not?

Chris Vine

unread,
Aug 9, 2016, 6:05:36 PM8/9/16
to
It is not a defect. The destructor for any object must always run when
the object is destroyed, say by going out of scope.

Typically the destructor for a moved object will do nothing because
its resources have been moved out, but it may still have residuary
resources to relinquish. The move constructor and assignment operator
for a class must ensure that any object of the class is still in a
state after the move which is capable of such destruction. It is also
wise to design the class so that it can still be assigned to after the
move.

Chris

Paavo Helde

unread,
Aug 9, 2016, 6:34:28 PM8/9/16
to
It's not defect, it's a feature!

In more detail, the C++ ideology prescribes that the object should be in
a valid state during the whole of its lifetime. In case of auto/stack
objects this means from the point of their definition until the end of
their scope. This is in contrast to e.g. C where often a bunch of
uninitialized (invalid state) variables is created right in the
beginning of the function, and the pointer values continue to hold now
invalid values after they have been free()-d, for example.

In par with this C++ ideology, the moved-from objects must still remain
in a valid state, as the name of the object is still in scope. This
means they may still be accessed, for example a new value may be
assigned to the object. If the object were left in invalid state, then
in general it could not be assigned a new value (unless it is a POD).

As the object is in a valid state, the destructor still needs to be
called when the object lifetime ends, to clean up that valid state. If
the object has been moved-from, this destructor typically does nothing
much, like deletes a nullptr (which is a non-op), but depending on the
exact state of the object it might do more.

The above is the philosophical point of view. This is reinforced by the
technical viewpoint: how should the scope cleanup code know if the
variable has been moved-from or not? It might have been moved in one
code branch and not in another. Even if moved-from, it might have been
assigned a new value after that which needs destruction. The simplest
solution is just to call the destructor always when an object goes out
of scope, and to rely on this destructor to do the right thing according
to the current valid state of the object.

> and what is point of moving around, if not?

The point is that the resources managed by the object have been moved
away, so the destructor has much less to do.

HTH
Paavo


Melzzzzz

unread,
Aug 9, 2016, 6:56:32 PM8/9/16
to
It's solved in Rust pretty clean.

It might have been moved in
> one code branch and not in another. Even if moved-from, it might have
> been assigned a new value after that which needs destruction. The
> simplest solution is just to call the destructor always when an
> object goes out of scope, and to rely on this destructor to do the
> right thing according to the current valid state of the object.

That also means that there is unnecessary code for maintaining state
and also run time errors in case that object is moved twice...
(what one should do if constructing from already moved object?)
In Rust moving is done in the compiler and compiler gives errors if
moved object is used after move (except assignment). Also compiler
doesn't call destructors on moved objects so there is no need to worry
about that.


>
> > and what is point of moving around, if not?
>
> The point is that the resources managed by the object have been moved
> away, so the destructor has much less to do.

Well, yes, but it would be nicer if there would not be need for
housekeeping ;)

>
> HTH
> Paavo
>
>
Thank you!

Melzzzzz

unread,
Aug 9, 2016, 6:57:53 PM8/9/16
to
On Tue, 9 Aug 2016 23:05:18 +0100
Chris Vine <chris@cvine--nospam--.freeserve.co.uk> wrote:

It is also
> wise to design the class so that it can still be assigned to after the
> move.
>
It looks to me like half baked solution.

> Chris

Thanks!

Chris Vine

unread,
Aug 9, 2016, 7:20:59 PM8/9/16
to
I don't think I would say it is half-baked. At any rate, it is
consistent with the C++ design philosophy.

It is also convenient. Not all resources maintained by a class should
necessarily be moved out. Furthermore, since in C++ any object can
be moved from by casting it to rvalue (ie not only temporaries), if a
moved-from object is subsequently assigned to, all its newly acquired
resources will need to be cleaned up again. No doubt you could have a
language which requires a compiler which deals with (and optimises for)
this automatically, but that is not what C++ does.

It is not really a problem to write classes this way. I am relaxed
about it. You very soon get used to it.

Melzzzzz

unread,
Aug 9, 2016, 7:27:42 PM8/9/16
to
Well, I'll live with it, just I am spoiled by Rust ;)

red floyd

unread,
Aug 9, 2016, 7:29:37 PM8/9/16
to
No, it's not a defect.

You have
* A constructed
* B move constructed
* B destructed
* A destructed

red floyd

unread,
Aug 9, 2016, 7:31:15 PM8/9/16
to
Never mind. I thought he was asking why the move constructor destroyed
the moved object.

Öö Tiib

unread,
Aug 9, 2016, 7:49:24 PM8/9/16
to
BINGO! C++ is never fully baked solution. It is always just a tool-set
and materials from what *you* can make the stove, dough and other
things that you may be think you need for baking and have your best
baked solution. Additionally it is assumed that you are pyromaniac,
aren't you? So there is always some bear traps with TNT in every
component of C++.

Paavo Helde

unread,
Aug 9, 2016, 7:59:22 PM8/9/16
to
On 10.08.2016 1:56, Melzzzzz wrote:
> On Wed, 10 Aug 2016 01:34:15 +0300
> Paavo Helde <myfir...@osa.pri.ee> wrote:
>> The
>> simplest solution is just to call the destructor always when an
>> object goes out of scope, and to rely on this destructor to do the
>> right thing according to the current valid state of the object.
>
> That also means that there is unnecessary code for maintaining state
> and also run time errors in case that object is moved twice...
> (what one should do if constructing from already moved object?)

As the object is still in a valid state, it can be freely moved from
again, and one typically gets a copy of the empty/sentinel value (an
empty string, for example).

> In Rust moving is done in the compiler and compiler gives errors if
> moved object is used after move (except assignment). Also compiler
> doesn't call destructors on moved objects so there is no need to worry
> about that.

So the house-keeping is essentially done by Rust compiler. This is
another ideology which might work as well. It's just not in the spirit
of C++ (as it seems to take away some ways to shoot one's legs off ;-)
(together with some loss of control and flexibility)).

Just for curiosity, what does Rust do when an object is moved-from in
one code branch and not in another? A compiler error?

Cheers
Paavo

Rick C. Hodgin

unread,
Aug 9, 2016, 8:19:34 PM8/9/16
to
What does this syntax mean?

A(A && moved) {
/* code here */
}

I've never seen it before.

Best regards,
Rick C. Hodgin

Melzzzzz

unread,
Aug 9, 2016, 8:37:32 PM8/9/16
to
I don't understand? In Rust everything is moved by default. That is if
compiler finds that value is moved it always complaints if there is
other place where value is used.
In such cases, one should make copy of value , which is in Rust
implementing Clone trait and providing clone function.
Of course, there is also Copy trait (derived from Clone) if calling
clone is not convenient.

One is not required to implement Clone as there is #[derive(Clone)]
directive for automatic derivation, inspired by Haskell.

Nobody

unread,
Aug 9, 2016, 10:48:48 PM8/9/16
to
On Tue, 09 Aug 2016 17:19:21 -0700, Rick C. Hodgin wrote:

> What does this syntax mean?
>
> A(A && moved) {
> /* code here */
> }
>
> I've never seen it before.

It's a move constructor.

A&& is an rvalue reference, introduced in C++11.

Basically, move constructors allow you to avoid deep-copying an object
that's about to be destroyed or overwritten. The move constructor can
just steal any dynamically-allocated data from "moved" rather than copying
it.

woodb...@gmail.com

unread,
Aug 10, 2016, 12:03:52 AM8/10/16
to
I'd say "adopt" rather than "just steal".

"Adopt" is shorter and the word "just" before
"steal" makes stealing sound like a small thing.


“There are four character types among people. One who says,
‘What’s mine is mine and what’s yours is yours’ is of
average character, and some say, this is the character of Sodom.
[One who says] ‘What’s mine is yours and what’s yours is mine’
is unlearned (lit., [of] the people of the land).
[One who says] ‘What’s mine is yours and what’s yours is yours’
is pious.
[One who says] ‘What’s yours is mine and what’s mine is mine’
is wicked.”

http://torah.org/learning/pirkei-avos-chapter5-13/

> it.

Brian
Ebenezer Enterprises
http://webEbenezer.net

Öö Tiib

unread,
Aug 10, 2016, 3:32:59 AM8/10/16
to
On Wednesday, 10 August 2016 03:37:32 UTC+3, Melzzzzz wrote:
> On Wed, 10 Aug 2016 02:59:10 +0300
> Paavo Helde <myfir...@osa.pri.ee> wrote:
>
> >
> > Just for curiosity, what does Rust do when an object is moved-from in
> > one code branch and not in another? A compiler error?
>
> I don't understand? In Rust everything is moved by default. That is if
> compiler finds that value is moved it always complaints if there is
> other place where value is used.

It is possible to make code where proof that moved value is not used
can take very long time. Compiler can't hang on such code. So there must
be situation when it is uncertain. Now at that point it is interesting
if Rust's compiler does reject code when it can prove that moved value
is used or it does reject code when it can not prove that used value is
not moved or you don't know what it does.

David Brown

unread,
Aug 10, 2016, 4:14:24 AM8/10/16
to
On 10/08/16 02:19, Rick C. Hodgin wrote:
> What does this syntax mean?
>
> A(A && moved) {
> /* code here */
> }
>
> I've never seen it before.
>

It is almost always written "A&& " rather than "A && ", just as "A&" is
used for a lvalue reference (the kind C++ has always supported) rather
than writing "A &". I certainly thought it looked strange with the
extra space.

rvalue references are new to C++11. They let you get a reference to
something that is anonymous or temporary, and let you "move" data around
rather than copying it. In particular, if you have an object that owns
some data or resource, and you "move" it somewhere else (such as into a
function), then you move ownership - the called function becomes
responsible for the object.

<http://thbecker.net/articles/rvalue_references/section_01.html>
<http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html>
<https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers>

rvalue references are somewhat hard to grasp - they are one of the
hardest new features of C++11. But they can make a big difference to
the efficiency of some kinds of code, and are therefore useful when
making class libraries. However, if you are just using classes, you can
use something like the unique_ptr<T> classes without understanding the
"magic" behind the scenes that make them work.


David Brown

unread,
Aug 10, 2016, 4:24:42 AM8/10/16
to
On 10/08/16 06:03, woodb...@gmail.com wrote:
> On Tuesday, August 9, 2016 at 9:48:48 PM UTC-5, Nobody wrote:
>> On Tue, 09 Aug 2016 17:19:21 -0700, Rick C. Hodgin wrote:
>>
>>> What does this syntax mean?
>>>
>>> A(A && moved) {
>>> /* code here */
>>> }
>>>
>>> I've never seen it before.
>>
>> It's a move constructor.
>>
>> A&& is an rvalue reference, introduced in C++11.
>>
>> Basically, move constructors allow you to avoid deep-copying an object
>> that's about to be destroyed or overwritten. The move constructor can
>> just steal any dynamically-allocated data from "moved" rather than copying
>
> I'd say "adopt" rather than "just steal".

"Adopt" is a better word for this than "steal", but that is because
during a "move" the source actively gives the data to the destination,
and the destination adopts responsibility for the data and resources.

Trying to apply human morality to programming terms makes it look like
you don't understand morality, and you don't understand programming.
/Programmers/ can be moral or amoral - programs cannot.


Melzzzzz

unread,
Aug 10, 2016, 5:04:40 AM8/10/16
to
I think that Rust compiler just complains if it sees any code path
where value is moved regardless.

Öö Tiib

unread,
Aug 10, 2016, 6:09:03 AM8/10/16
to
So you think that following code will be rejected by Rust?
I write just pseudo-code, I don't know Rust. Comment "..."
means code that does not deal with neither 'condition' nor 'variable':

if ( condition )
{
// ...

move( variable );

// ...
}

// ...

unless ( condition )
{
// ...

use( variable ); // <- we can't reach that with moved 'variable'

// ...
}

Öö Tiib

unread,
Aug 10, 2016, 6:11:30 AM8/10/16
to
On Wednesday, 10 August 2016 11:24:42 UTC+3, David Brown wrote:
>
> Trying to apply human morality to programming terms makes it look like
> you don't understand morality, and you don't understand programming.
> /Programmers/ can be moral or amoral - programs cannot.

That can be viewed as existential risk for humankind. Stephen Hawking
talks about it:
http://www.dailymail.co.uk/sciencetech/article-3406751/Hawking-Threats-human-survival-likely-new-science.html
Some even research that risk:
http://cser.org/research/current-projects/
IOW ... depends how to look at if ... if it is a problem, or if it is
misunderstanding.

Melzzzzz

unread,
Aug 10, 2016, 6:24:46 AM8/10/16
to
On Wed, 10 Aug 2016 03:08:28 -0700 (PDT)
[bmaxa@maxa-pc examples]$ cat moved.rs
struct A;
impl Drop for A{
fn drop(&mut self) {
println!("destructor");
}
}

fn main() {
let a = A;
if false {
let b = a;
} else {
}
if false {
let c = a;
}
}
[bmaxa@maxa-pc examples]$ rustc moved.rs
error[E0382]: use of moved value: `a`
--> moved.rs:15:13
|
11 | let b = a;
| - value moved here
...
15 | let c = a;
| ^ value used here after move
|
= note: move occurs because `a` has type `A`, which does not implement the `Copy` trait

error: aborting due to previous error

Daniel

unread,
Aug 10, 2016, 9:47:57 AM8/10/16
to
On Wednesday, August 10, 2016 at 4:24:42 AM UTC-4, David Brown wrote:
>
> /Programmers/ can be moral or amoral - programs cannot.

Sure they can. A deep learning program could be taught to be moral, with Old
Testament training sets. After training and testing, it could be set to
control sensors that activated weapons systems.

Daniel

10 When thou comest nigh unto a city to fight against it, then proclaim peace
unto it....

13 And when the Lord thy God hath delivered it into thine hands, thou shalt
smite every male thereof with the edge of the sword:

14 But the women, and the little ones, and the cattle, and all that is in the
city, even all the spoil thereof, shalt thou take unto thyself; and thou shalt
eat the spoil of thine enemies, which the Lord thy God hath given thee.

15 Thus shalt thou do unto all the cities which are very far off from thee,
which are not of the cities of these nations.

16 But of the cities of these people, which the Lord thy God doth give thee
for an inheritance, thou shalt save alive nothing that breatheth:

Deuteronomy 20:10-16

15 And Moses said unto them, Have ye saved all the women alive?

16 Behold, these caused the children of Israel, through the counsel of Balaam,
to commit trespass against the Lord in the matter of Peor, and there was a
plague among the congregation of the Lord.

17 Now therefore kill every male among the little ones, and kill every woman
that hath known man by lying with him.

18 But all the women children, that have not known a man by lying with him,
keep alive for yourselves.

Numbers 31:15-18

Melzzzzz

unread,
Aug 10, 2016, 9:49:23 AM8/10/16
to
On Wed, 10 Aug 2016 06:47:19 -0700 (PDT)
Daniel <daniel...@gmail.com> wrote:

> On Wednesday, August 10, 2016 at 4:24:42 AM UTC-4, David Brown wrote:
> >
> > /Programmers/ can be moral or amoral - programs cannot.
>
> Sure they can. A deep learning program could be taught to be moral,
> with Old Testament training sets. After training and testing, it
> could be set to control sensors that activated weapons systems.
>
Could it be said that malware is amoral?

Adam C. Emerson

unread,
Aug 10, 2016, 1:40:54 PM8/10/16
to
On 2016-08-10, Melzzzzz <m...@zzzzz.com> wrote:
> I don't understand? In Rust everything is moved by default. That is if
> compiler finds that value is moved it always complaints if there is
> other place where value is used.
> In such cases, one should make copy of value , which is in Rust
> implementing Clone trait and providing clone function.
> Of course, there is also Copy trait (derived from Clone) if calling
> clone is not convenient.
>
> One is not required to implement Clone as there is #[derive(Clone)]
> directive for automatic derivation, inspired by Haskell.
>

I think the problem you're having is that you assume that C++ 'move'
construction and assignment are analogous to Rust's notion of
ownership. Rust's ownership is a compile time property managing
lifetimes and keeping track of when data can be safely disposed of.

In C++, 'move' isn't really an operation. You have a special kind of
reference (an rvalue reference) and you can overload functions,
including the constructor and assignment operators, to behave
differently when they receive an rvalue reference.

The rvalue overload overloads of the constructor and assignment
operators are traditionally called 'move' constructors and 'move'
assignment operators. Both are allowed to do whatever you want, though
if you write some weird move constructor you will probably have great
sorrow and heaps of infelicity when you go to use your class with the
standard library or other code.

To be useful and integrate well with the rest of the language, the
move constructor MUST leave the 'moved-from' object in a valid
state. It doesn't have to be 'empty' it just has to be a valid
object. You will generally only bother writing a move constructor if
you can get some use out of it. So, say, if your class contains a
pointer to allocated memory, you might copy the pointer to the new
object and null it in the old (moved-from) object.

C++'s idea of movement makes no sense at all in Rust, since in C++ you
have to manage your own memory and manage lifetimes on your own and
all that other stuff. So, while they might not be what you're used to
or what would make sense in other languages, move semantics make sense
within the general shape of C++ as a language and as a set of
practices (like RAII).

Melzzzzz

unread,
Aug 10, 2016, 2:08:44 PM8/10/16
to
On Wed, 10 Aug 2016 17:40:29 -0000 (UTC)
"Adam C. Emerson" <az...@fox.blue> wrote:

>
> C++'s idea of movement makes no sense at all in Rust, since in C++ you
> have to manage your own memory and manage lifetimes on your own and
> all that other stuff.

Rust is same as C++ regarding memory management. It's just that
unique_ptr is build into language... Box::new(Type) allocates on heap
and returns unique_ptr. There are also structs like Rc which are
actually shared_ptr. Resources are managed by RAII. Actually
Rust in the beginning had GC but that was later ditched from language,
I guess because of performance reasons...




Melzzzzz

unread,
Aug 10, 2016, 2:16:02 PM8/10/16
to
On Wed, 10 Aug 2016 20:08:28 +0200
Melzzzzz <m...@zzzzz.com> wrote:

> Box::new(Type) allocates on heap

No, Box::new(data), as it is just like any other function...

Richard Damon

unread,
Aug 11, 2016, 11:09:06 PM8/11/16
to
On 8/9/16 5:29 PM, Melzzzzz wrote:
> Given following program,
>
> #include <cstdio>
> #include <utility>
>
> class A{
> public:
> A() {
> printf("constructor\n");
> }
> A(A && moved) {
> printf("move constructor\n");
> }
> ~A(){
> printf("destructor\n");
> }
> };
>
> int main() {
> A a;
> A b = std::move(a);
> }
>
> I would expect that destructor of `a` wouldn't be called but:
> [bmaxa@maxa-pc examples]$ g++ moved.cpp
> [bmaxa@maxa-pc examples]$ ./a.out
> constructor
> move constructor
> destructor
> destructor
> [bmaxa@maxa-pc examples]$
>
> Is this defect, and what is point of moving around, if not?
>

One very simple reason. At the point the object life time ends, how can
the compiler be certain, in all cases, if the object has been moved from?

There might be multiple paths, some which move and some which don't.
A reference to the object might have been passed to a function, which
you can't see into at this point in the code, so that might have moved
or might not have.

The only solution I can think of to fully bypass the destructor if the
object was moved from is to add a flag to every object to remember this.
Much simpler to just call the destructor.

Also, the move constructor is ALLOWED, but not required to take the
resource out of the object. It is perfectly valid for a move constructor
to be identical in function to a copy constructor, so the destructor
does need to be run to make sure the resources are freed.

SG

unread,
Aug 24, 2016, 7:57:51 AM8/24/16
to
Yup. I've been programming many years in C++ and started playing with
Rust since 2 or 3 years ago. Rust feels like the cleaner and simpler
C++ to me. Rust's move semantics is an example of simplicity and nice
defaults. Keep in mind that in C++ a move constructor can even through
an exception. This makes things complicated and necessitates helper
functions like std::move_if_noexcept if you're interested in the
strong exception safety guarantee.

So, yeah, after you move from something in Rust, it's really gone in
the sense that no destructor will run. In C++ you might have to add
a kind of "zombie state" to your type's invariants your object has to
be aware of so that it knows what and what not to do during
destruction. On the other hand, this allows C++ programmers to move
things out from behind references and pointers which isn't possible in
safe Rust. Safe Rust only allows stack-local objects as source of
moves because that's how it knows when *not* to "destroy" something
that is already gone. It keeps track of it statically or via runtime
drop flags (if runtime dependent branches are involved). But this is
usually not a restriction in Rust. You can compensate for this with a
richer container API -- for example, a vec::<T>::pop for popping off
the last element of a vector...

// C++
std::vector<complicyted_type> v = ...;
auto mine = std::move(v.back());
v.pop();

// Rust
let v: Vec<ComplicatedType> = ...;
let mine = v.pop();

Kudos to the people who are responsible for Rust!

Bo Persson

unread,
Aug 24, 2016, 1:24:00 PM8/24/16
to
On 2016-08-24 13:57, SG wrote:
> On Wednesday, August 10, 2016 at 1:27:42 AM UTC+2, Melzzzzz wrote:
>> On Wed, 10 Aug 2016 00:20:43 +0100
>> Chris Vine <chris@cvine--nospam--.freeserve.co.uk> wrote:
>>
>>>
>>> It is not really a problem to write classes this way. I am relaxed
>>> about it. You very soon get used to it.
>>
>> Well, I'll live with it, just I am spoiled by Rust ;)
>
> Yup. I've been programming many years in C++ and started playing with
> Rust since 2 or 3 years ago. Rust feels like the cleaner and simpler
> C++ to me. Rust's move semantics is an example of simplicity and nice
> defaults. Keep in mind that in C++ a move constructor can even through
> an exception. This makes things complicated and necessitates helper
> functions like std::move_if_noexcept if you're interested in the
> strong exception safety guarantee.
>

On the other hand, this doesn't forbid storing a Type in a std::vector,
even if Type's move constructor throws on odd Thursdays in october on
leap-years.

Flexibility before simplicity. :-)


Bo Persson



SG

unread,
Aug 25, 2016, 8:33:09 AM8/25/16
to
Right. And I'm really glad that noexcept/move_if_noexcept along with
type traits to test "noexceptability" made it into C++11. It was an
important add-on to C++'s move semantics story.

> Flexibility before simplicity. :-)

I don't think of possibly throwing move constructors as a feature but
more of an accidental consequence of language design decisions that
made things more complicated than it has to be. All of the C++11/14
object types I can think of that have a dedicated move constructor but
a possibly throwing one are due to composition with legacy types:
Think std::pair<std::string,Foo> where Foo is a legacy type that is
not move-optimized but std::string offers a nice move constructor we'd
like to make use of if such a pair is moved. This pair's move
constructor cannot be marked noexcept in case Foo has a throwing copy
constructor. So, basically, we have throwing move constructors because
we have throwing copy constructors and making types movable requires
a class author's interaction in case the author already wrote a custom
destructor.

In a language like Rust this is not something anybody needs to worry
about because every value of every data type automatically moves
efficiently without any failures. The flexibility you lose is the
ability to define a custom action that is taken when a value of a
user-defined type moves. While this may sound inflexible I struggle to
come up with compelling examples where this would be an issue. The
only example I can come up with is Boost's linked_ptr. It's a kind of
shared ownership smart pointer which keeps track of all owners not by
allocating a shared reference counter but by linking itself with other
owners in a circular list. So, it needs to be aware of when it moves
to where. But this kind of smart pointer vanished some time ago for
some reason (performance/threading/lack of a weak_linked_ptr/...?)
And in other cases that I can't come up with, an additional layer of
indirection is probably part of the solution. Given the potential
rarity of such scenarios, I think it's fair to say that this
restriction is insignificant.

Cheers!
SG

me

unread,
Aug 25, 2016, 11:59:34 AM8/25/16
to
Ack. But I think it's really more fundamental than that. The simple fact
is: the moved-from object _still_exists_, so it must be destructed. Even
after the move, there are still two objects. Typically, the destructor
will only have to do trivial things, as pointers to data should have been
nulled during the move, other resources have been shifted over to the new
object, etc, but the object exists until destructed.
0 new messages