|
|
|
Sponsored Link •
|
|
Advertisement
|
"Programmers who overload unary operator& should be sentenced to writing libraries that need to operate properly when fed such classes." - Peter Dimov, esteemed Boost member, Boost newsgroup June 2002
Although that's very funny, it's also a pretty strong statement. Why is there such antipathy to the use of this operator?
In this chapter we'll look at some of the problems that doing this can cause. Our solution will be an unexciting one, simply the recommendation that you follow Peter's implicit advice, and forswear any use of this overload for shortsighted gains and avoid much grief down the line.
One thing I should make clear. Anytime in this chapter that I
refer to operator &() it will be the unary form,
which is the address of operator. The binary form, which is the
bitwise AND operator, is an entirely different beast.
class Int
{
Int operator &(Int const &); // Bitwise operator
void *operator &() // Address-of operator
. . .
Like most C++ operators, you're free to return anything you like
from operator &(). This means that you can alter the
value, or the type, or both. This can be a powerful aid in rare
circumstances, but it can also cause you a world of trouble.
construct()
and destroy() methods, whose canonical definitions
are as follows:
template <typename T>
struct some_allocator
{
. . .
void construct(T* p, T const &x)
{
new(p) T(x);
}
void destroy(T* p)
{
p->~T();
}
. . .
The construct() method is used by containers to in-
place construct elements, as in
template <typename T, . . . >
void list::insert(. . ., T const &x)
{
. . .
Node *node = . . .
get_allocator().construct(&node->value, x);
If you're storing in the list overloads operator
&() to return a value that cannot be converted to
T* then this line will fail to compile.
operator
&. There are quite a number of classes that overload it,
including CComBSTR, CComPtr and
CComVariant.
To account for the incompatibility between ATL types and STL
containers, the designers of ATL introduced the
CAdapt template, which attempts to solve the problem
by containing an instance of its parameterising type. It then
provides implicit conversion operators and comparison operations
to allow it to be used in place of its parameterising type.
Because CAdapt<T> does not overload operator
&(), it can be used to mask the overload for any
T that does.
template <typename T>
class CAdapt
{
public:
CAdapt();
CAdapt(const T& rSrc);
CAdapt(const CAdapt& rSrCA);
CAdapt &operator =(const T& rSrc);
bool operator <(const T& rSrc) const
{
return m_T < rSrc;
}
bool operator ==(const T& rSrc) const;
operator T&()
{
return m_T;
}
operator const T&() const;
T m_T;
};
Unfortunately, this is just a sticking plaster on a broken arm.
As we saw in Chapter 23, templates that inherit from their
parameterising type have a deal of trouble in unambiguously
providing access to the requisite constructors of their parent
class. The same problem exists for types such as
CAdapt, which enhance their parameterising type via
containment rather than inheritance. All the constructors of
T, except the default and copy constructors, are
inaccessible. This clutters your code, reduces the applicability
of generic algorithms, and prevents the use of RAII (see Section
3.5).
template<typename T>
T *get_real_address(T &t)
{
return reinterpret_cast<T*>(&reinterpret_cast<byte_t &>(t));
}
There are other complications, to account for
const and/or volatile, but that's the
essence of it. The Boost libraries have a nifty
addressof() function, which takes account of all the
issues.
But the use of reinterpret_cast is cause for some
concern. The standard (C++-98: 5/2.10;3) says: "the mapping
performed . is implementation-defined. [Note: it is intended to be
unsurprising to those who know the addressing structure of the
underlying machine]". Since the result may conceivably not be
valid, it's not possible to claim that this technique is truly
portable. However, it's also pretty hard to imagine a compiler
that would not perform the expected conversion.
We can now side step types with pathological operator
&() overloads, but this would require peppering all our
code with calls to the real address shim. But it's ugly, and its
correctness is implementation-defined. Do you want to use a
standard library with myriad reinterpret_casts?
|
Sponsored Links
|