Topic: A portable way to implement assignment


Author: Rintala Matti <bitti@cs.tut.fi>
Date: 2000/03/09
Raw View
I've discussed with some colleagues about implementing assignment
using copy construction. In several places I've seen the construct

X& X::operator =(const X& r)
{
  X tmp(r);
  swap(tmp); // Swap contents of *this and tmp
  return *this;
}

Which is nice and has copy-or-rollback if swap is guaranteed to
succeed. However, the problem is that it requires the swap-operation,
which is not necessarily plausible for all classes.

A couple of weeks ago I got an idea to write assignment in the
following way:

X& X::operator =(const X& r)
{
  if (this != &r) // Guard against self-assignment
  {
    this->~X();
    new(this) X(r);
  }
  return *this;
}

I.e. the idea is to use explicit destructor call and placement new to
first delete the old object and then create a new one in its
place. Has anyone else come across this idea? I've tried to check the
standard, and it seems that this is indeed a portable and
standard-conforming way, as converting "this" into a void* guarantees
that the resulting pointer points to the beginning of the memory
containing the object.

------------ Matti Rintala ----------- bitti@cs.tut.fi ------------
"Well, the way I see it, logic is only a way of being ignorant
 by numbers." (from "Small Gods" by Terry Pratchett)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Darin Adler <darin@bentspoon.com>
Date: 2000/03/09
Raw View
In article <w59n1o9prh8.fsf@korppi.cs.tut.fi>, Rintala Matti
<bitti@cs.tut.fi> wrote:

> A couple of weeks ago I got an idea to write assignment in the
> following way:
>
> X& X::operator =(const X& r)
> {
>   if (this != &r) // Guard against self-assignment
>   {
>     this->~X();
>     new(this) X(r);
>   }
>   return *this;
> }

This has been proposed many times before. But it's not exception-safe.
If the constructor for X fails, then the object pointed to by this is
left in a bad state (already destroyed and not recreated).

    -- Darin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: roger_orr@my-deja.com
Date: 2000/03/09
Raw View
In article <w59n1o9prh8.fsf@korppi.cs.tut.fi>,
  Rintala Matti <bitti@cs.tut.fi> wrote:
>
> I've discussed with some colleagues about implementing assignment
> using copy construction. In several places I've seen the construct
>
> X& X::operator =(const X& r)
> {
>   X tmp(r);
>   swap(tmp); // Swap contents of *this and tmp
>   return *this;
> }
>
> Which is nice and has copy-or-rollback if swap is guaranteed to
> succeed. However, the problem is that it requires the swap-operation,
> which is not necessarily plausible for all classes.
>
> A couple of weeks ago I got an idea to write assignment in the
> following way:
>
> X& X::operator =(const X& r)
> {
>   if (this != &r) // Guard against self-assignment
>   {
>     this->~X();
>     new(this) X(r);
>   }
>   return *this;
> }
>
> I.e. the idea is to use explicit destructor call and placement new to
> first delete the old object and then create a new one in its
> place. Has anyone else come across this idea? I've tried to check the
> standard, and it seems that this is indeed a portable and
> standard-conforming way, as converting "this" into a void* guarantees
> that the resulting pointer points to the beginning of the memory
> containing the object.
>
> ------------ Matti Rintala ----------- bitti@cs.tut.fi ------------
> "Well, the way I see it, logic is only a way of being ignorant
>  by numbers." (from "Small Gods" by Terry Pratchett)
>

Two initial problems spring to mind
1) This technique will _not_ be exception safe - if you get an
exception during the copy ctor you're dead - likely to get double
deletion of your object during stack unwind.
2) As written you would have severe problems with derived classes - you
do have a virtual dtor in base classes don't you :)
You'd need to ensure the inplace destructor is called like:-
  this->X::~X();
to prevent destructing the whole derived object.

I'm sure there are other pros and cons this initial reply has avoided...

Roger Orr.


Sent via Deja.com http://www.deja.com/
Before you buy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Dave Abrahams <abrahams@mediaone.net>
Date: 2000/03/09
Raw View
in article w59n1o9prh8.fsf@korppi.cs.tut.fi, Rintala Matti at
bitti@cs.tut.fi wrote on 3/9/00 5:41 AM:

> A couple of weeks ago I got an idea to write assignment in the
> following way:
>
> X& X::operator =(const X& r)
> {
>   if (this != &r) // Guard against self-assignment
>   {
>     this->~X();
>     new(this) X(r);
>   }
>   return *this;
> }
>
> I.e. the idea is to use explicit destructor call and placement new to
> first delete the old object and then create a new one in its
> place. Has anyone else come across this idea? I've tried to check the
> standard, and it seems that this is indeed a portable and
> standard-conforming way, as converting "this" into a void* guarantees
> that the resulting pointer points to the beginning of the memory
> containing the object.

It's standard conforming, but probably not exception-safe. If the placement
new-expression should throw an exception, base classes and members of this
are likely to be double-destroyed, resulting in undefined behavior. An
explicit destructor call in code that is not actually managing the destroyed
object's lifetime is always a grave error.

unlike-james-bond-c++-objects-can-only-die-once-ly y'rs,
Dave

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Rintala Matti <bitti@cs.tut.fi>
Date: 2000/03/10
Raw View
roger_orr@my-deja.com writes:
> In article <w59n1o9prh8.fsf@korppi.cs.tut.fi>,
>   Rintala Matti <bitti@cs.tut.fi> wrote:
> > X& X::operator =(const X& r)
> > {
> >   if (this != &r) // Guard against self-assignment
> >   {
> >     this->~X();
> >     new(this) X(r);
> >   }
> >   return *this;
> > }
>
> Two initial problems spring to mind
> 1) This technique will _not_ be exception safe - if you get an
> exception during the copy ctor you're dead - likely to get double
> deletion of your object during stack unwind.

Yes, this I already know (but forgot to say so in the article). In
this scheme it is essential that the copy constructor always
succeeds. Otherwise the program probably attempts to destroy the
object again.

> 2) As written you would have severe problems with derived classes - you
> do have a virtual dtor in base classes don't you :)

Ah, but in practise you will always have problems if you invoke only
base class assignment in a derived class object.

> You'd need to ensure the inplace destructor is called like:-
>   this->X::~X();
> to prevent destructing the whole derived object.

I'm not too sure this would work. But in this case the derived classes
could implement a similar assignment operator, which would take care
of both the base and derived class operations.

Or you could have

class X
{
public:
  virtual ~X();
  X& operator =(const X& r)
  {
    if (typeid(*this) == typeid(r))
    { // Assignment of similar objects
      if (this != &r)
      { // Not self-assignment
        this->~X();
        r.placement_clone(this);
      }
    }
    else
    {
      throw AssigningDifferentObjects();
    }
    return *this;
  }
private:
  virtual void placement_clone(void* ptr)
  {
    new(ptr) X(*this);
  }
// Rest of the class
};

class Y : public X
{
public:
  Y& operator =(const Y& r)
  {
    X::operator =(r);
    return *this;
  }
private:
  virtual void placement_clone(void* ptr)
  {
    new(ptr) Y(*this);
  }
};

In this example the base class assignment does the whole operation (I
didn't run the code through compiler, so there may be typos or other
mistakes).

But anyway, I asked the question because of academic interest. I fully
agree that because the lack of exception safety the approach should
not be used in real-life programming.

------------ Matti Rintala ----------- bitti@cs.tut.fi ------------
"Well, the way I see it, logic is only a way of being ignorant
 by numbers." (from "Small Gods" by Terry Pratchett)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 2000/03/10
Raw View
In article <8a6m60$bgk$1@nnrp1.deja.com>, roger_orr@my-deja.com writes
>I'm sure there are other pros and cons this initial reply has avoided...

Well except for relatively simple classes, dtors and ctors are
expensive. The problem isn't the base memory but the superstructure that
you have to first completely dismember and then recreate.


Francis Glassborow      Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: kanze@gabi-soft.de
Date: 2000/03/13
Raw View
Dave Abrahams <abrahams@mediaone.net> writes:

|>  in article w59n1o9prh8.fsf@korppi.cs.tut.fi, Rintala Matti at
|>  bitti@cs.tut.fi wrote on 3/9/00 5:41 AM:

|>  > A couple of weeks ago I got an idea to write assignment in the
|>  > following way:

|>  > X& X::operator =(const X& r)
|>  > {
|>  >   if (this != &r) // Guard against self-assignment
|>  >   {
|>  >     this->~X();
|>  >     new(this) X(r);
|>  >   }
|>  >   return *this;
|>  > }

|>  > I.e. the idea is to use explicit destructor call and placement new to
|>  > first delete the old object and then create a new one in its
|>  > place. Has anyone else come across this idea? I've tried to check the
|>  > standard, and it seems that this is indeed a portable and
|>  > standard-conforming way, as converting "this" into a void* guarantees
|>  > that the resulting pointer points to the beginning of the memory
|>  > containing the object.

|>  It's standard conforming, but probably not exception-safe. If the
|>  placement new-expression should throw an exception, base classes and
|>  members of this are likely to be double-destroyed, resulting in
|>  undefined behavior. An explicit destructor call in code that is not
|>  actually managing the destroyed object's lifetime is always a grave
|>  error.

It's not only not exception safe, and doesn't work well in the case of
inheritance, it can also pose serious problems when used in a
multi-threaded environment.

There is one particular case where the alternative is very difficult:
when using virtual inheritance, and multiple assignment to the base
object is not an option.  The only problem is, that if you are using
multiple inheritance, you are using inheritance, i.e.: one of the cases
where the idiom causes particular problems.  And of course, I, for one,
would question a design in which multiple assignment of the base class
caused problems.

--
James Kanze                               mailto:kanze@gabi-soft.de
Conseils en informatique orient   e objet/
                   Beratung in objektorientierter Datenverarbeitung
Ziegelh   ttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: David Townsend <townsend@synopsys.com>
Date: 2000/03/15
Raw View
Rintala,

Have you seen Herb Sutter's new book "Excpetional C++" - I belive this
very same code appears in the book and he explains why this is no good!

I don't have the book in front of me, but email me back and I'll dig up the
reference for you.

dave.

Rintala Matti wrote:

> I've discussed with some colleagues about implementing assignment
> using copy construction. In several places I've seen the construct
>
> X& X::operator =(const X& r)
> {
>   X tmp(r);
>   swap(tmp); // Swap contents of *this and tmp
>   return *this;
> }
>
> Which is nice and has copy-or-rollback if swap is guaranteed to
> succeed. However, the problem is that it requires the swap-operation,
> which is not necessarily plausible for all classes.
>
> A couple of weeks ago I got an idea to write assignment in the
> following way:
>
> X& X::operator =(const X& r)
> {
>   if (this != &r) // Guard against self-assignment
>   {
>     this->~X();
>     new(this) X(r);
>   }
>   return *this;
> }
>
> I.e. the idea is to use explicit destructor call and placement new to
> first delete the old object and then create a new one in its
> place. Has anyone else come across this idea? I've tried to check the
> standard, and it seems that this is indeed a portable and
> standard-conforming way, as converting "this" into a void* guarantees
> that the resulting pointer points to the beginning of the memory
> containing the object.
>
> ------------ Matti Rintala ----------- bitti@cs.tut.fi ------------
> "Well, the way I see it, logic is only a way of being ignorant
>  by numbers." (from "Small Gods" by Terry Pratchett)
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]


======================================= MODERATOR'S COMMENT:

Please do not over-quote. It is never necessary to quote the signature
and the moderation trailer in a followup post.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]