c++ primer笔记13

1. Defining the Generic Handle Class

To create a Handle , a user will be expected to pass the address of a dynamically allocated object of the type (or a type
derived from that type) managed by the Handle . From that point on, the Handle will “own” the given object. In particular, the Handle class will assume responsibility for deleting that object once there are no longer any Handle s attached to it. Once an object is bound to a Handle, the user must not delete that object.

template <class T> class Handle {
public:
// unbound handle
Handle(T *p = 0): ptr(p), use(new size_t(1)) { }
// overloaded operators to support pointer behavior
T& operator*();
T* operator->();
const T& operator*() const;
const T* operator->() const;
// copy control: normal pointer behavior, but last Handle deletes the object
Handle(const Handle& h): ptr(h.ptr), use(h.use)
{ ++*use; }
Handle& operator=(const Handle&);
~Handle() { rem_ref(); }
private:
T* ptr; // shared object
size_t *use; // count of how many Handle spointto *ptr
void rem_ref()
{ if (--*use == 0) { delete ptr; delete use; } }

};

template <class T>
inline Handle<T>& Handle<T>::operator=(const Handle &rhs)
{
++*rhs.use; // protect against self-assignment
rem_ref(); // decrement use count and delete pointers if needed
ptr = rhs.ptr;
use = rhs.use;
return *this;
}

2. Using the Handle

We intend this class to be used by other classes in their internal implementations. However, as an aid to understanding how the Handle class works, we’ll look at a simpler example first. This example illustrates the behavior of the Handle by allocating an int and binding a Handle to that newly allocated object:

{ // new scope
// user allocates but must not delete the object to which the Handle is attached
Handle<int> hp(new int(42));
{ // new scope
Handle<int> hp2 = hp; // copies pointer; use count incremented
cout << *hp << " " << *hp2 << endl; // prints 42 42
*hp2 = 10; // changes value of shared underlying int
} // hp2 goes out of scope; use count is decremented
cout << *hp << endl; // prints 10
} // hp goes out of scope; its destructor deletes the int

As an example of using Handle in a class implementation, we might reimplement our Sales_item class. This version of the class defines the same interface, but we can eliminate the copy-control members by replacing the pointer to Item_base by a Handle:

class Sales_item {
public:
// default constructor: unbound handle
Sales_item(): h() { }
// copy item and attach handle to the copy
Sales_item(const Item_base &item): h(item.clone()) { }
// no copy control members: synthesized versions work
// member access operators: forward their work to the Handle class
const Item_base& operator*() const { return *h; }
const Item_base* operator->() const
{ return h.operator->(); }
private:
Handle<Item_base> h; // use-counted handle
};

3. *** Template Specializations ***

It is not always possible to write a single template that is best suited for every possible template argument with which the template might be instantiated. In some cases, the general template definition is simply wrong for a type. The general definition might not compile or might do the wrong thing. At other times, we may be able to take advantage of some specific knowledge
about a type to write a more efficient function than the one that is instantiated from the template. Let’s look again at our compare function template. If we call this template definition on two const char* arguments, the function compares the pointer values.

template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}

4. Specializing a Function Template

A template spacialization is a separate definition in which the actual type(s) or value(s) of one or more template parameter(s) is (are) specified. The declaration for the specialization must match that of the corresponding template.

// special version of compare to handle C-style character strings
template <>
int compare<const char*>(const char* const &v1,
const char* const &v2)
{
return strcmp(v1, v2);
}

Now when we call compare , passing it two character pointers, the compiler will call our specialized version. It will call the generic version for any other argument types (including plain char*,也就是说要完全匹配):

const char *cp1 = "world", *cp2 = "hi";
int i1, i2;
compare(cp1, cp2); // calls the specialization
compare(i1, i2); // calls the generic version instantiated with int

As with any function, we can declare a function template specialization without defining it. A template specialization declaration looks like the definition but omits the function body. If the template arguments can be inferred from the function parameter
list, there is no need to explicitly specify the template arguments:

// error: invalid specialization declarations
// missing template<>
int compare<const char*>(const char* const&, const char* const&);

// error: function parameter list missing
template<> int compare<const char*>;

// ok: explicit template argument const char* deduced from parameter types
template<> int compare(const char* const&, const char* const&);

5. Function Overloading versus Template Specializations

Omitting the empty template parameter list, template<> , on a specialization may have surprising effects. If the specialization syntax is missing, then the effect is to declare an overloaded nontemplate version of the function:

// generic template definition
template <class T>
int compare(const T& t1, const T& t2) { /* ... */ }
// OK: ordinary function declaration
int compare(const char* const&, const char* const&);

The definition of compare does not define a template specialization. Instead, it declares an ordinary function with a return type and a parameter list that could match those of a template instantiation.

We’ll look at the interaction of overloading and templates in more detail in the next section. For now, what’s important to know is that when we define a nontemplate function, normal conversions are applied to the arguments. When we specialize a template, conversions are not applied to the argument types. In a call to a specialized version of a template, the argument type(s) in the call must match the specialized version function parameter type(s) exactly. If they don’t, then the compiler will instantiate an instantiation for the argument(s) from the template definition.

If a program consists of more than one file, the declaration for a template specialization must be visible in every file in which the specialization is used. As with other function declarations, declarations for template specializations should be included in a header file. That header should then be included in every source file that uses the specialization.

6. Ordinary Scope Rules Apply to Specializations

Before we can declare or define a specialization, a declaration for the template that it specializes must be in scope. Similarly, a declaration for the specialization must be in scope before that version of the template is called:

// define the general compare template
template <class T>
int compare(const T& t1, const T& t2) { /* ... */ }

int main() {
// uses the generic template definition
int i = compare("hello", "world");
// ...
}

// invalid program: explicit specialization after call
template<>
int compare<const char*>(const char* const& s1, const char* const& s2)
{ /* ... */ }