Exception Safety w/ Copy & Swap Idiom

I'm currently working on a small project using exceptions. I've been looking into the copy and swap idiom and I have a few questions.

First, how do you write a swap that does not throw? Must all of your members be pointers? What about a std::string?

Second, if you have an abstract base class that happens to contain members, how do you get around not being able to create a copy of an abstract base class? Should I create a strictly-interface-only abstract and an intermediate class for the other common members to the rest of the hierarchy?

Thanks.
First, how do you write a swap that does not throw? Must all of your members be pointers? What about a std::string?


You can have the members as reference or as pointers, which ever you prefer. By reference would, perhaps, be more C++ -like way.
1
2
3
4
5
6
void Swap(YourType &a, YourType &b) throw()
{
	YourType Tmp = a;
	a = b;
	b = Tmp;
}


Or a more general template solution:
1
2
3
4
5
6
7
template <class T>
void Swap(T &a, T &b) throw()
{
	T Tmp = a;
	a = b;
	b = Tmp;
}


Or a Swap with pointers. This also works for std::string, although std::string has a built-in swap()
1
2
3
4
5
6
void Swap(YourType *const a, YourType *const b) throw()
{
	YourType Tmp = *a;
	*a = *b;
	*b = Tmp;
}


The keyword "throw" in the function declaration dictates that the Swap() cannot throw.

Second, if you have an abstract base class that happens to contain members, how do you get around not being able to create a copy of an abstract base class?


You are not meant to make a copy of an abstract class object, since it can't be instantiated.

If you like, you can make a pure virtual Copy() function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Base
{
	virtual ~Base();
	virtual void Copy(Base const &) = 0;
	std::string Data;
};

void Base::Copy(Base const &Rhs)
{
	Data = Rhs.Data;
}

struct Derivee : public Base
{
	virtual void Copy(Data const &) {} // This actually never gets called
	virtual void Copy(Derivee const &);
	std::string MoreData;
};

void Derivee::Copy(Derivee const &Rhs)
{
	Base::Copy(Rhs);
	MoreData = Rhs.MoreData;
}


As in the example, there can be an implementation for a pure virtual function, like Base::Copy() here. Because Base cannot be instantiated, it can't obviously be called directly by operator .* dereference.

But you can call it from deriving classes.

It's been a while since I've done anything like this, so I don't know if this is the recommended textbook approach. Somebody might want to correct me here.

EDIT: Fixed syntax
Last edited on
Those swap examples are exactly what I find confusing:
1
2
3
4
5
6
7
8
template <class T>
void Swap(T &a, T &b) throw()
{
	T Tmp = a; // this allocates memory and T's operations could still throw,
                   // couldn't they?
	a = b;
	b = Tmp;
}

The throw keyword doesn't prevent it from throwing somehow, does it? If it does, then why not do the following?
1
2
3
4
5
template <class T>
void Swap(T &a, T &b) throw()
{
    std::swap( a, b );
}

Last edited on
// this allocates memory and T's operations could still throw,
// couldn't they?


Yes, T's copy constructor might throw, but Swap will not. You should handle the catch() in Swap rather than pass it on. Actually I just realised that this would be better syntax:
T Tmp(a);


If it does, then why not do the following?

That's possible if you want to use std::swap() that doesn't throw. But then you accept the consequences of what happens in your code when memory allocation fails and the values are not swapped :)
Writing a swap() that doesn't throw is the $64,000 question. It cannot be done generically, for exactly
the reason moorecm pointed out above: the copy construction could throw. But a well-designed
one should not throw unless an out of memory condition occurs (insert my usual dissertation on
ignoring those errors as impossible to recover from/not worth the trouble) since all it does is make a
copy of an object.
the copy construction could throw
Isn't it supposed to be bad practice to throw inside a constructor because the object is left in an uncertain state?
This still seems a little silly, then.

What if you made two copies of the object and wrote a swap function that takes the second one to use as the temporary? In that case, the memory would already be allocated prior to the swap. If there was a bad_alloc exception, the object would still be left in a valid state.

For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct A
{
    // assume a valid copy constructor is here

    A & operator=( const A & instance );
private:
    void swap_using_temp( A & copy, A & temp ) throw();

    int a;
};

A & A::operator=( const A & instance )
{
    A copy( instance ),
      temp( instance );
    // by this point, the memory was successfully allocated
    swap_using_temp( copy, temp );
    return *this;
}

void A::swap_using_temp( A & copy, A & temp ) throw()
{
    // now, assuming the members are primitives, assignment shouldn't throw
    // and there's no allocation going on here, either
    temp.a = a;
    a = copy.a;
    copy.a = temp.a;
    // however, why swap these at all when you could just assign them and
    // discard the old values?!
}
Last edited on
helios wrote:

Isn't it supposed to be bad practice to throw inside a constructor because the object is left in an uncertain state?


No, the C++ standard guarantees/mandates that any members that were initialized prior to the exception
will be destroyed in reverse order of initialization. Note that members are initialized either explicitly in
the initializer list or implicitly before the constructor body is entered. Members cannot be initialized
inside the body of the constructor; there they can only be assigned. It is for this reason that it is
recommended to do as much work as possible in the initializer list as opposed to the body, as otherwise
exceptions occurring in the constructor could result in the object be undestroyable, but this is the result of
poor programming habits, not the language.


One source of confusion is that assignment should be implemented in terms of copy and swap. Swap
therefore must not be implemented as assignment, unless the assignment is at a finer level:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Foo {
    int x, y, z;

    Foo& operator=( Foo rhs )
        { swap( *this, rhs ); return *this; }

    friend void swap( Foo&, Foo& );
};

// This is wrong, because it calls Foo::operator=, which calls this, which calls...
void swap( Foo& one, Foo& two ) {
    Foo tmp( one );
    one = two;
    two = tmp;
}

// This is right:
void swap( Foo& one, Foo& two ) {
    using std::swap;
    swap( one.x, two.x );
    swap( one.y, two.y );
    swap( one.z, two.z );
}


Now swap( Foo&, Foo& ) will not throw if swapping of its members can't throw.
This "inductive" coding technique is how you write a swap() function that does not throw.
Ultimately, every swap() call boils down to swapping of fundamental types which we
know won't throw. As soon as swap attempts to allocate memory, it no longer provides
the strong exception guarantee.


I must add that swap should not throw and it is the responsibility of the class designer to ensure that.
Topic archived. No new replies allowed.