Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Template-based implementation of Sutter's exception-safe operator= idiom

Reply
Thread Tools

Template-based implementation of Sutter's exception-safe operator= idiom

 
 
Mikhail N. Kupchik
Guest
Posts: n/a
 
      08-19-2004
Hi All.

I have a question regarding Herb Sutter's idiom of implementation of
operator= via nonthrowing swap() member function, to guarantee strict
exception safety.

The idea of the idiom is shown by the example:

-- code fragment 1 ----------------------------------------------------
class MyClass
{
...
public:
void swap( MyClass& ) throw();

MyClass& operator=( const MyClass& rvalue )
{
MyClass other( rvalue ); // (1)
swap( other );
return *this;
}
};
-- end of code fragment 1 ---------------------------------------------

Thus we need to implement only copy constructor (used by (1)) and get
strict exception safety: every possible exception in operator= is
produced by (1) and gets thrown out before `this' object is actually
modified.

My question is the following: is it possible to implement the idiom of
exception-safe operator= via template class (with little help of CRTP
of course), so that I could write for many of my classes:

-- code fragment 2 ----------------------------------------------------
class MyClass
: public CanonicalReplacement< MyClass >
{
...
public:
void swap( MyClass& ) throw();
};
-- end of code fragment 2 ---------------------------------------------

My first attempt was

-- code fragment 3 ----------------------------------------------------
template< typename T >
struct CanonicalReplacement
{
T& operator=( const T& other )
{
if( this != &other ) // (2)
{
T temp_object( other ); // (3)
T::swap( temp_object ); // (4)
}
return *this;
}
};
-- end of code fragment 3 ---------------------------------------------

Besides of one slippery thing that in (2) `this' is implicitly upcasted
to CanonicalReplacement<T>* and actually pointers to CanonicalReplacement<T>
rather than T are compared (I'm in doubt if most compilers can optimize
all pointer arithmetic that arises here), there is one bigger trouble:
the code simply does not work, as the operator= is actually declared for
argument types ( CanonicalReplacement<T>&, const T& ), not ( T&, const T& );
thus, compiler generates default version of it for class T.

The following example illustrates it:

-- code fragment 4 ----------------------------------------------------
struct A
{
A& operator=( const A& ) { cout << "operator= of A\n"; }
};

struct B
: public CanonicalReplacement< A >
{
A a_;

B() { }
B( const B& ) { cout << "cctor of B\n"; }
void swap() throw() { cout << "swap of B\n"; }
};

int main()
{
B() = B(); // (5)
};
-- end of code fragment 4 ---------------------------------------------

In (5), if CanonicalReplacement<B>:perator= is called, it will print
`cctor of B' in (3) then `swap of B' in (4).
But actually default B:perator= is generated, it calls A:perator= for
`a_' field and prints `operator= of A'.

Unfortunately it is not possible to use Barton-Nackman method of restricted
template expansion with aid of friend member function (see code fragment 5),
because operator= must be a member function (according to section 13.5.3.1
of the ISO standard for C++ programming language).

-- code fragment 5 ----------------------------------------------------
template< typename T >
struct CanonicalReplacement
{
friend T& operator=( T& lvalue, const T& rvalue )
{
if( &lvalue != &rvalue )
{
T temp_object( rvalue );
lvalue.swap( temp_object );
}
return lvalue;
}
};
-- end of code fragment 5 ---------------------------------------------

The idion can be implemented via preprocessor, but this is a hack of course.
Does anybody have any other ideas on this?

-- Mikhail Kupchik

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
 
 
 
Mikhail N. Kupchik
Guest
Posts: n/a
 
      08-20-2004
Fix: of course I mean

struct B
: public CanonicalReplacement< B >

in code fragment 4.

-- Mikhail Kupchik

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
 
 
 
David Abrahams
Guest
Posts: n/a
 
      08-20-2004
http://www.velocityreviews.com/forums/(E-Mail Removed) (Mikhail N. Kupchik) writes:

> Hi All.
>
> I have a question regarding Herb Sutter's idiom of implementation of
> operator= via nonthrowing swap() member function, to guarantee strict
> exception safety.


Herb doesn't deserve the blame for that technique (though maybe for
recommending it a bit too heartily). The swapping assignment operator
is almost always a bad idea, especially in a template, because it
spends cycles on the strong guarantee at what may be the wrong level
of granularity.

(http://lists.boost.org/MailArchives/boost/msg36928.php)

Any time you make assignment give the strong guarantee by copying and
swapping, you force anyone who wants to use assignment in an operation
which doesn't need that strong guarantee to pay for the unneccessary
copy, which could be very expensive.

--
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
James Hopkin
Guest
Posts: n/a
 
      08-20-2004
Oops: in my other reply I left in a paragraph about std::swap, which I
meant to remove.

Default std::swap is definitely *not* what we want, as that will
recursively call the assignment operator until the end of time.

James

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
James Hopkin
Guest
Posts: n/a
 
      08-20-2004
(E-Mail Removed) (Mikhail N. Kupchik) wrote in message news:<(E-Mail Removed). com>...
>
> Does anybody have any other ideas on this?
>


I'm not sure I'd want to do this (reasons listed below), but I can
suggest an implementation.

// T requirements: no-throw swap(T&, T&)
// no copy assignment operator defined

template< typename T >
struct CanonicalReplacement
{

BOOST_STATIC_ASSERT(boost::is_base_and_derived<Can onicalReplacement,
T>::value);

CanonicalReplacement& operator=(const CanonicalReplacement&
other)
{
T& derived_this = static_cast<T&> (*this);
const T& derived_other = static_cast<const T&>(other);

if(&derived_this != &derived_other) // this line is an
optimisation only
{
T temp(derived_other);

swap(derived_this, temp);
}
return *this;
}
};

This can be *privately* inherited, since assignment operators don't
get inherited anyway.

I used a namespace-scope swap, because this is more general. You can
always write a global swap to call a member one.

I'm calling a global std::swap, since this is more general (if you
want a member version to be used, you can define a global swap which
calls the member one).

To be extra safe, you can use boost::address_of rather than the &
operator, just in case the built-in & has been overridden.


As for why I wouldn't do this:

1) Assignment by swapping with temporary is a well-known idiom
(largely thanks to Herb - correct me if I'm wrong). Most coders will
immediately understand an assignment operator written this away, but
may puzzle for a while seeing this base. Of course, that problem goes
away if it were to become an accepted idiom (chicken and egg
situation).

2) Ease of mis-use: if the client defines a copy assignment
operator in T or any derived class without removing this base, things
go silently wrong. I can't think of a way of preventing this without
overhead.

3) Personal taste: I wouldn't want to use a base class to do
something so simple.

4) Potential inheritance overheads: with ideal compilers this
method would always be zero overhead, but in the real world we know
that's not always the case, particularly if you needed to inherit
other classes.

5) Compilation time: I would imagine instantiating this template
class everywhere would have some impact on compile times.


Cheers,
James

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
Steven E. Harris
Guest
Posts: n/a
 
      08-21-2004
David Abrahams <(E-Mail Removed)> writes:

> Any time you make assignment give the strong guarantee by copying
> and swapping, you force anyone who wants to use assignment in an
> operation which doesn't need that strong guarantee to pay for the
> unneccessary copy, which could be very expensive.


For those who do need the strong guarantee at a higher level, how do
they go about getting it? I understand the "SGI argument" about
concurrency control, but I'm missing the analogous option here to add
exception-safe "locks" at a higher level.

Take your example where a client wants an assignment followed by
push_back() to have the strong guarantee. Is this a potential
solution?


// Neither compiled nor tested.

template <typename C, typename T>
C& safe_assign_push(C& dest, C const& src, T const& val)
{
C temp( src );
temp.push_back( val );
dest.swap( temp );
return dest;
}


Footnotes:
http://lists.boost.org/MailArchives/boost/msg36928.php

--
Steven E. Harris

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
Mikhail N. Kupchik
Guest
Posts: n/a
 
      08-21-2004
(E-Mail Removed) (James Hopkin) wrote in message news:<(E-Mail Removed) om>...
> (E-Mail Removed) (Mikhail N. Kupchik) wrote in message news:<(E-Mail Removed). com>...
> >
> > Does anybody have any other ideas on this?
> >

>
> I'm not sure I'd want to do this (reasons listed below), but I can
> suggest an implementation.


Hi.

Default operator= in T calls operator= for all base classes and fields, not only
for CanonicalReplacement< T >.

The program below

-- code fragment 6 ----------------------------------------------------

template< typename T >
struct CanonicalReplacement
{
CanonicalReplacement& operator=(const CanonicalReplacement& other)
{
T& derived_this = static_cast<T&> (*this);
const T& derived_other = static_cast<const T&>(other);

if(&derived_this != &derived_other) // this line is an optimisation only
{
T temp(derived_other);

derived_this.swap(temp);
}
return *this;
}
};

struct A
{
A& operator=( const A& ) { cout << "A:perator= (should not be called)\n"; }
};

struct B
: public CanonicalReplacement< B >
{
A a_;
B() { }
void swap( B& ) throw() { cout << "B::swap()\n"; }
B( const B& ) { cout << "cctor of B\n"; }
};

int main()
{
B() = B();
}

-- code fragment 6 ----------------------------------------------------

prints

cctor of B
B::swap()
A:perator= (should not be called)

-- Mikhail Kupchik

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
Glen Low
Guest
Posts: n/a
 
      08-23-2004
> In (5), if CanonicalReplacement<B>:perator= is called, it will print
> `cctor of B' in (3) then `swap of B' in (4).
> But actually default B:perator= is generated, it calls A:perator= for
> `a_' field and prints `operator= of A'.


The problem is copy constructors are not inherited; it only seems that
way because if you don't define a copy constructor, the compiler tries
to chain the superclass one with the ones for each member.

You can kludgify it by creating an Replace member in
CanonicalReplacement<B> to do the copy and swap idiom, then calling
this from the defined copy constructor in B. Or even make Replace a
function template like std::swap is.

The only other thing I can think of (which is subtly icky on other
levels) is to reverse the inheritance:

1. Put all the core B stuff into B_core.
2. Make B inherit from CanonicalReplacement<B_core>, and ensure B
itself has no member variables etc.
3. Regularize your other constructors (that's the icky part).

Cheers,
Glen Low, Pixelglow Software
www.pixelglow.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Insertion Sort : C++ implementation 100 times slower than C implementation sanket C++ 7 11-03-2011 05:00 AM
Knowing the implementation, are all undefined behaviours become implementation-defined behaviours? Michael Tsang C Programming 54 03-30-2010 07:46 AM
Knowing the implementation, are all undefined behaviours become implementation-defined behaviours? Michael Tsang C++ 32 03-01-2010 09:15 PM
Which exception idiom do you prefer? stevengarcia@yahoo.com Java 6 02-28-2006 06:39 PM
Idiom Question - Finding Instances Rhino Java 5 02-18-2004 05:36 AM



Advertisments