The Artima Developer Community
The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next
Sponsored Link

The C++ Source
What's Your Address
by Matthew Wilson
April 8, 2005

Page 1 of 3  >>

Advertisement

Summary
This article is an excerpt from Matthew Wilson's recently-published book, Imperfect C++, Addison-Wesley, 2004.

"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
  . . .

26.1 Can't Get The Real Address

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.

26.1.1 STL Containment

The standard library containers store contained elements via in- place construction. For instance, containers following the Vector model [Aust1999, Muss2001] maintain a block of memory within which each element is stored contiguously. Since they are resizable, there needs to be a mechanism to add and remove elements from this storage, which is provided by the allocators. The Allocator [Aust1999, Muss2001] model includes the 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.

26.1.2 ATL Wrapper Classes & CAdapt

One of the major beefs I have with Microsoft's Active Template Library (ATL)—which, like most frameworks, started out with high ideals—is the heavy overloading of 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.

Listing 26.1

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).

26.1.3 Getting The Real Address

So is there a way to get at the real address? Since there is no equivalent overloadable operator for eliciting a reference from an object, we can use some dubious reference casting to get our address, along the lines of the following attribute shim (see Chapter 20):
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?

Page 1 of 3  >>

The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next

Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us