Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 26.  What's Your Address?


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 (C++-98: 23.2.4) 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 concept [Aust1999, Muss2001] 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 the type, T, that 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 and 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 parameterizing type. It then provides implicit conversion operators and comparison operations to allow it to be used in place of its parameterizing 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 Band-Aid on a broken arm. As we saw in Chapter 23, templates that inherit from their parameterizing 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 parameterizing 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, "[T]he 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 sidestep 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 implemented with myriad reinterpret_casts?


      Previous section   Next section