|
Boost : |
From: Darin Adler (darin_at_[hidden])
Date: 1999-12-10 20:05:26
I'm now confused about how to correctly implement a non-throwing swap so
that the standard library algorithms will automatically use it. I've posted
a message to comp.std.c++ about this issue. Once we reach a consensus there,
I'll fix <smart_ptr.hpp> to do it correctly.
Enclosed is a copy of what I posted to the news group. People who read Boost
can help me out by joining the discussion on comp.std.c++, unless there's a
compelling reason to discuss it hear as well.
Something I got wrong in the message is that I mistakenly say that std::less
is a function template, when it's actually a class template and thus doesn't
suffer from the same problem as std::swap.
-- Darin
----------
Because I've read Herb Sutter's excellent description of the swap technique
for implementing an exception-safe operator= (in Guru of the Week and his
new book Exceptional C++), most of my classes have a member function named
swap. Up until now, I've been implementing it like this:
// header file
#include <algorithm>
namespace N {
class U
{
public:
// ...
void swap(U&);
U& operator=(const U&);
// ...
};
}
namespace std {
template<> inline void swap(N::U& one, N::U& other)
{
one.swap(other);
}
}
// implementation file
namespace N {
void U::swap(U& other)
{
std::swap(m1, other.m1);
std::swap(m2, other.m2);
}
U& U::operator=(const U& other)
{
U copy(other);
swap(copy);
return *this;
}
}
The idea here is to make the efficient non-throwing version of swap defined
in the member function available to the standard library implementation by
specializing the std::swap function template. The more-efficient swap will
be used any time a generic algorithm swaps two objects of this type. In
fact, I suggested a change to iter_swap to ensure that this optimization
will be performed as often as possible. Andrew Koenig submitted a library
defect report about that which is currently active:
<http://anubis.dkuug.dk/JTC1/SC22/WG21/docs/lwg-active.html#187>.
Recently, someone pointed out to me that the swap definition in the above
code is illegal. I thought that I was specializing a swap function template,
but in fact this is not possible. As I understand it, there's no such thing
as a specialization of a function template. It's possible to overload a
function template, but not specialize it. After hearing this, I am not even
sure if the syntax is correct in the code above.
Worse, the standard [17.4.3.1/1] allows me to specialize a template in a
standard header, but does not allow me to overload a function with a new
definition in namespace std, even if it's a function template. So the swap
operation must move out of namespace std. Herb's chapter "Name Lookup,
Namespaces, and the Interface Principle" in Effective C++ pushes me further
in this direction, suggesting that a function like swap belongs in namespace
N alongside the class U to get the best results with Koenig lookup.
This creates a few new problems. Code that explicitly calls std::swap won't
get the more efficient version defined for class U -- as I understand it,
Koenig lookup doesn't apply when you specify the namespace explicitly. When
I tested with my compiler, I didn't even get the appropriate swap when I put
a "using std::swap" in; the using directive seemed to interfere with Koenig
lookup.
I hate the idea that if the overload of swap is not found, the code will
work but be inefficient and possibly throw an exception where one is not
expected. U::swap above may have this problem because it calls std::swap on
m1 and m2, and either of these may have a more efficient non-throwing
version of swap. Since I want Koenig lookup to work and so can't explicitly
qualify std::swap, I have to make sure that the member function named swap
doesn't hide the global function named swap. So it may be best to omit the
member function completely.
Here's the "corrected" version of class U.
// header file
namespace N {
class U
{
public:
// ...
friend void swap(U&, U&);
U& operator=(const U&);
// ...
};
}
// implementation file
#include <algorithm>
namespace N {
void swap(U& one, U& other)
{
using namespace std;
swap(one.m1, other.m1);
swap(one.m2, other.m2);
}
U& U::operator=(const U& other)
{
U copy(other);
swap(*this, copy);
return *this;
}
}
As I understand it, the same issues apply to specializations of std::less
and some other function templates in the library.
At this point I have a few questions:
1) Is the version of std::swap that I define in my original example
above illegal?
2) Is there a better way to write swap for U (preferably without "using
namespace std;")? The desire is that it will use an overloaded
implementation of swap found by Koenig lookup in another namespace for each
member, but fall back on std::swap if such a function does not exist.
3) Should I deprecate use of "std::swap" and "using std::swap" in all my
programs (at least in any generic parts)? It seems this may be necessary
since either of these forms will result in a call to the less efficient
standard library function template implementation of swap even when a more
efficient overload is defined as "part of" the class. (Note that I am using
"part of" in the sense described in Exceptional C++.)
4) Is there a guarantee that algorithms in the standard library will
call swap without an explicit "std::" qualifier? The library implementation
can probably omit the "std::" since the implementation is normally inside
namespace std. If it does not use "std::", it seems it will pick up the
non-throwing swap implementation from the namespace that the class is
defined in, due to Koenig lookup. If the library is allowed to explicitly
call "std::swap" instead, it would really bum me out. I might even go so far
as to say it constitutes a defect in the standard.
5) What else did I get wrong in the discussion above?
-- Darin
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk