(Click here, next to the previous article)
So what should we do? We still need a method that allows others to call swap and get a more efficient template-specific (template-specific) version. The answer is simple. We still declare a non-member (non-member) Swap to call the member (member) Swap, but do not declare the non-member (non-member) as STD :: specialization or overloading of swap ). For example, if our widget-related (widget-related) functions are all in namespace widgetstuff, it looks like this:
Namespace widgetstuff {
... // Templatized widgetimpl, etc.
Template <typename T> // as before, including the swap
Class widget {...}; // member function
...
Template <typename T>// Non-member swap function;
Void swap (widget <t> &,// Not part of the STD namespace
Widget <t> & B)
{
A. Swap (B );
}
}
Now, if some code uses two widget objects to call swap, C ++'s Name Lookup rules (Name Lookup Rules)Argument-dependent Lookup(Search for parameter dependencies) orKoenig Lookup(Koenig search) is known for its rule.) The widget-specific (for the widget) version in widgetstuff will be found. This is exactly what we want.
This method works well for both classes and class templates, so we should always use it. Unfortunately, there is still a motivation to make STD: swap special for classes (I will talk about it later ), so if you want your swap class-specific (Class-specific) version to be called in as many contexts as possible (and you did ), you need to write a non-member (non-member) version in the namespace where your class is located, and provide a specialization (special) for STD: swap ).
By the way, if you do not use namespaces (namespace), everything mentioned above will still apply (that is, you still need a non-member (non-member) swap to call Member (member) Swap), but why do you need to put your class (class), template (Template), function (function), Enum, enumerant (enumeration) (I have used two words (Enum, enumerant) here. I don't know what the difference is.-note the translator) and typedef names are all heap in global namespace (global namespace? Do you think it is appropriate?
Everything I have written so far applies to swap writers, but there is a situation worth looking at from the customer's point of view. Suppose you have written a function template (function template) where you want to exchange two objects values ):
Template <typename T>
Void dosomething (T & obj1, T & obj2)
{
...
Swap (obj1, obj2 );
...
}
Which swap will be called? General version in STD, you know it must exist; a specialization (special) of general version in STD may or may not exist; t-specific (t-specific) version, it may or may not exist. It may be in a namespace or not in a namespace (but certainly not in STD ). Which one should be called? If the T-specific (t-specific) version exists, you want to call it. If it does not exist, go back and call the general version in STD. You can meet your wishes as follows:
Template <typename T>
Void dosomething (T & obj1, T & obj2)
{
Using STD: swap;// Make STD: swap available in this function
...
Swap (obj1, obj2); // call the best swap for objects of type T
...
}
When the compiler sees this swap call, it will look for the correct swap to call. C ++'s Name Lookup rules (Name Lookup rule) ensures that it can be found in the global namespace (global namespace) or the same namespace (namespace) as T) t-specpacific (t-specific) swap in. (For example, if T is a widget in namespace widgetstuff, the compiler will find swap in widgetstuff using argument-dependent Lookup (parameter dependency lookup .) If T-specific (t-specific) Swap does not exist, the compiler will use swap in STD, thanks to using Declaration which makes STD: swap visible in this function. However, the compiler prefers T-specific (t-specific) STD: swap specialization (special) compared with general templates, So If STD :: if swap is specially modified for t, the version will be used.
It is so easy to get the correct swap call. One thing you need to be careful not to limit the call, because this will affect the way C ++ determines the function to be called. For example, if you write a call to swap like this,
STD ::Swap (obj1, obj2); // the wrong way to call swap
This will force the compiler to only consider the SWAp in STD (including any template specializations (template-specific), so it will exclude the more suitable T-specific (t-specific) for obtaining a definition elsewhere) the possibility of a version. Alas, Some Misleading programmers use this method to limit the calls to swap, which is also very important for your classes to fully specialize in STD: swap: it allows the code written in this misleading way to use the type-specific (type-specific) Swap implementation. (Such code still exists in some standard library implementations, so it will help you to help such code work as efficiently as possible .)
So far, we have discussed the default swap, member (member) SWAps, non-member (non-member) SWAps, STD: swap specializations (special ), so let's summarize the current situation.
First, if the default Implementation of swap provides acceptable performance for your class or class template, you do not need to do anything. Anyone who tries to switch your objects type will get the support of the default version and can work well.
Second, if swap's default implementation is inefficient (which almost always means that your class or template uses a variant of pimpl idiom), follow these steps:
- Provides a public (public) Swap member function (member function) that can efficiently exchange values of two objects of your type ). This function should never throw an exception out of the motivation I will explain in a short time ).
- Provide a non-member (non-member) swap in the same namespace (namespace) where your class (class) or template (Template) is located. Use it to call your swap member function (member function ).
- If you have written a class (class) (not a class template), you will specialize in STD: swap for your class (class. Let it also call your swap member function (member function ).
Finally, if you call swap, make sure to include a using declaration so that STD: swap is visible in your function, and then do not use any namespace (namespace) restrictions when you call swap.
The only unsolved problem is my warning-never let the member (member) version of swap throw exceptions (exception ). This is because one of swap's most important applications is to provide powerful exception-Safety (exception Security) guarantees for classes (classes) (and class templates (class templates. Item 29 will provide all the details, but this technology based on swap's member (member) version will never throw an exception assumption. This force constraint applies only to the member (member) version! It cannot be applied to non-member (non-member) versions, because the default version of swap is based on copy construction (copy construction) and copy assignment (copy assignment ), in general, both functions are allowed to throw an exception. If you write a custom version of swap, you must not only provide a more efficient method for exchanging values, but also ensure that this method does not throw an exception. As a general rule, the two swap features will be closely integrated, because efficient swaps are almost always based on built-in types (built-in type) (For example, pointer under pimpl idiom), but the built-in types (built-in type) operation will never throw an exception.
Things to remember
- If STD: swap is inefficient for your type, provide a swap member function (member function ). And make sure that your swap will not throw an exception.
- If you provide a member (member) Swap, provide a non-member (non-member) swap that calls the member (member) at the same time. For classes (classes) (non-templates (templates), you must also specify STD: swap.
- When swap is called, use a using Declaration for STD: swap, and do not use any namespace (namespace) restrictions when SWAp is called.
- It is no problem to completely specialize STD templates for user-defined types (user-defined types), but never try to add anything new to STD.