Reading Notes Objective c ++ Item 25 implements a swap that does not throw exceptions and implements tiveswap
1. swap is so important
Swap is a very interesting function. It was first introduced as part of STL,It has become the mainstay of abnormal security programming (Item 29),It is also a common mechanism for coping with self-assignment in copying.(Item 11 ). Swap is very useful, and the proper implementation of swap is very important, along with the importance of some complications. In this clause, we will explore these complications and how to handle them.
2. swap's dummies implementation method and defect 2.1 default Implementation of swap Functions
The Swap function is to exchange the values of two objects, which can be achieved through the standard swap algorithm:
1 namespace std { 2 3 template<typename T> // typical implementation of std::swap; 4 5 void swap(T& a, T& b) // swaps a’s and b’s values 6 7 { 8 9 T temp(a);10 11 a = b;12 13 b = temp;14 15 }16 17 }
As long as your type supports copy (copy constructor and copy assignment operator), the default swap implementation does not require you to do some special work to support it.
2.2 defects of the default Implementation of swap functions-potentially inefficient
However, the default swap implementation may not be excited. It includes three copies: Copy a to temp, copy B to a, and copy temp to B. For some types, these copies are not required. The default swap will pull you from the fast lane to the slow lane.
These types that do not need to be copied usually contain pointers, pointing to other types that contain real data. A common example using this design method is "pimpl idiom" (pointing to the implemented pointer Item 31). For example:
1 class WidgetImpl { // class for Widget data; 2 3 public: // details are unimportant 4 5 ... 6 7 private: 8 9 int a, b, c; // possibly lots of data —10 11 std::vector<double> v; // expensive to copy!12 13 ...14 15 };16 17 class Widget { // class using the pimpl idiom18 19 public:20 21 Widget(const Widget& rhs);22 23 Widget& operator=(const Widget& rhs) // to copy a Widget, copy its24 25 { // WidgetImpl object. For26 27 ... // details on implementing28 29 *pImpl = *(rhs.pImpl); // operator= in general,30 31 ... // see Items 10, Item 11, and Item 12.32 33 }34 35 ...36 37 private:38 39 WidgetImpl *pImpl; // ptr to object with this40 41 }; // Widget’s data
In order to exchange the values of two Widget objects, we only need to exchange two pImpl pointers, but the default swap algorithm cannot obtain these values. It not only copies three Widget objects, but also copies three WidgetImpl objects. Very inefficient, not freezing.
3. How to implement an efficient swap3.1 to define the fully-specialized version of swap for common classes
What we need to do is to tell std: swap that when the Widget object is swap, the method for executing swap is the pImpl pointer inside swap. That is, to customize a std: swap for the Widget. This is the most basic idea. Let's look at the following code, but it cannot be compiled ..
1 namespace std { 2 3 template<> // this is a specialized version 4 5 void swap<Widget>(Widget& a, // of std::swap for when T is 6 7 Widget& b) // Widget 8 9 {10 11 swap(a.pImpl, b.pImpl); // to swap Widgets, swap their12 13 } // pImpl pointers; this won’t14 15 compile16 17 }
"Templpate <>" indicates that this is a fully-specific template (total template specializaiton) for std: swap ), the "<Widget>" after the name indicates that this feature only applies to the Widget type of T. In other words, when a generalized swap template is applied to the Widget type, the above implementation method should be used. Generally,We do not allow modificationStdThe content of the namespace, but the type we created can be used to fully specialize the standard template..
However, this function cannot be compiled. This is because it tries to access pImpl pointers in a and B. They are private. We can declare our special functions as friend, but the traditional practice is as follows: declare a swap public member function that actually executes swap in the Widget, And let std: swap call the member function:
1 class Widget { // same as above, except for the 2 3 public: // addition of the swap mem func 4 5 ... 6 7 void swap(Widget& other) 8 9 {10 11 using std::swap; // the need for this declaration12 13 // is explained later in this Item14 15 swap(pImpl, other.pImpl); // to swap Widgets, swap their16 17 } // pImpl pointers18 19 ...20 21 };22 23 namespace std {24 25 template<> // revised specialization of26 27 void swap<Widget>(Widget& a, // std::swap28 29 Widget& b)30 31 {32 33 a.swap(b); // to swap Widgets, call their34 35 } // swap member function36 37 }
This method is not only compiled but consistent with the STL container. Both of them provide the public member function version for swap and the std: swap version for calling member functions.
3.2 define a special version of swap for the template class
However, if the Widget and WidgetImpl are replaced with a class template, we replace the Data Type stored in WidgetImpl with a template parameter:
1 template<typename T>2 3 class WidgetImpl { ... };4 5 template<typename T>6 7 class Widget { ... };
Implementing a swap member function in a Widget is as simple as the original one, but the specialization of std: swap is troublesome. Below is what we want to write:
1 namespace std { 2 3 template<typename T> 4 5 void swap<Widget<T> >(Widget<T>& a, // error! illegal code! 6 7 Widget<T>& b) 8 9 { a.swap(b); }10 11 }
The above code looks completely reasonable, but it is not legal. We try to partially specialize a template (std: swap ),Although class templates can be biased, function templates cannot be biased.. Therefore, this Code cannot be compiled (although some compiler errors have been compiled ).
When you want to "eccentric" a function template, a common method is to add an overloaded function.. As shown below:
1 namespace std { 2 3 template<typename T> // an overloading of std::swap 4 5 void swap(Widget<T>& a, // (note the lack of “<...>” after 6 7 Widget<T>& b) // “swap”), but see below for 8 9 { a.swap(b); } // why this isn’t valid code10 11 }
In general, you can reload a function template, but std is a special namespace and its rules are also very special.InStdBut you cannot add a new template (class, function, or anything else.The content of Std is entirely determined by the C ++ Standards Committee. Programs beyond this line can certainly be compiled and run, but the behavior is not defined. If you want your software to have predictable behavior, do not add new things to std.
What should we do? We still need a way for others to call our efficient template-specific version of swap. The answer is simple. We still declare a non-member swap that calls the member function swap, but we do not declare the non-member function as a feature or overload of std: swap. For example, the Widget-related functions are defined in the namespace WidgetStuff, as shown below:
1 namespace WidgetStuff { 2 3 ... // templatized WidgetImpl, etc. 4 5 template<typename T> // as before, including the swap 6 7 class Widget { ... }; // member function 8 9 ...10 11 template<typename T> // non-member swap function;12 13 void swap(Widget<T>& a, // not part of the std namespace14 15 Widget<T>& b)16 17 {18 19 a.swap(b);20 21 }22 23 }
Now, if you call swap anywhere, the name search policy (name lookup rules) in C ++ will search for the specified version of the Widget in WidgetStuff. This is exactly what we need.
4. The special version and non-member function version of swap in common classes must be provided
This method is equally effective for classes, so it seems that we should use it in any situation. Unfortunately, you also need to provide the specialized std: swap (explained later) version for the class, so if you want to call a specific version of the swap class in as many contexts as possible, you need to define the non-member function version of swap and the specific version of std: swap in the class namespace at the same time.
5. Search policy when swap is called
All I have implemented so far should belong to the swap author, but there is a situation worth noting from the customer's point of view. Suppose you are implementing a function template, and the function needs to perform swap on the values of the two objects:
1 template<typename T> 2 3 void doSomething(T& obj1, T& obj2) 4 5 { 6 7 ... 8 9 swap(obj1, obj2);10 11 ...12 13 }
Which version of swap will he call? Version in saved std? Which of the following versions of std may or may not exist? It may or may not exist. Maybe in a namespace or not in a namespace (definitely not in std) T specific version? All you need is to call a specific T version if there is one, and then call the normal version in std if not. To meet your needs:
1 template<typename T> 2 3 void doSomething(T& obj1, T& obj2) 4 5 { 6 7 using std::swap; // make std::swap available in this function 8 9 ...10 11 swap(obj1, obj2); // call the best swap for objects of type T12 13 ...14 15 }
When the compiler sees a call to swap, they will find the correct version of swap. The C ++ name search policy first searches for a specific version of T of swap in a global scope or in the same namespace. (For example, if T is a Widget in The namespace WidgetStuff, the compiler uses the parameter dependency search (argument-dependent lookup) to search for swap in WidgetStuff ). if no specific swap version exists, the compiler uses the swap version in std. Thanks to using std: swap, std: swap is visible in the function. However, the compiler prefers to specify the special version of T on the normal template std: swap. Therefore, if std: swap has already been converted to T, the special version will be called.
6. Do not add the std qualifier when swap is called.
Therefore, it is easy to call the correct swap version. One thing you need to note is not to limit the call, because this will affect c ++'s decision on which function to call. For example, if you call swap as follows:
1 std::swap(obj1, obj2); // the wrong way to call swap
You force the compiler to only consider the swap version in std (including all template-specific versions), so that it will not be able to call a more appropriate T-specific version defined elsewhere (if any ). Some Misleading programmers have indeed made such limits on swap calls. Therefore, it is important for your class to fully specialize std: swap: this allows misleading programmers to call certain types of swap versions even if they use an incorrect call method (with std restrictions.
7. Summary of swap implementation steps
Now we have discussed the default swap, the member function swap, the non-member function swap and the special version of std: swap, and the call to swap. Let's summarize:
First, if the default swap implementation version provided for your class or class template can meet your needs in terms of efficiency, you do not need to do anything. Anyone tries to perform swap on the object of your defined type, as long as the default version is called, this will work well.
Secondly, if swap's default implementation does not meet your requirements for efficiency (it usually means that your class or class template uses variables similar to those pointing to implementation (pimpl idiom ), follow the steps below:
Finally, if you are calling swap, make sure to include a using declaration in your function to make std: swap visible, when you call swap, do not add the std namespace to limit it.
8. Last warning -- Do not let the member function swap throw an exception
My last warning is never to let the swap member function version throw an exception. One of the most useful aspects of swap is that the help class (or class template) provides powerful exception security assurance. Item 29 has a detailed explanation, and the technology is based on the assumption that the swap member function version does not throw an exception. This constraint is only applicable to the member function version! Not for non-member function versions, because the default version of swap is based on the copy constructor and the copy value assignment operator. In general, both functions are allowed to throw an exception. When you implement a personalized version of swap,You don't just provide a pair of ValuesSwapYou also provide a function that does not throw exceptions.. As a general rule, the two features of swap always come together, because efficient swap is usually built on the basis of internal operation (such as the underlying pointer to the implementation), the built-in type will never throw an exception.
9. Summary
- When Using std: swap to perform swap on your custom type, if the efficiency is not high enough, provide a member function version and ensure that this function will not throw an exception.
- If you provide a member function swap and a non-member swap to call the member swap. Std: swap is specialized on classes (not templates.
- When swap is called, using std: swap declaration is used, and namespace restriction is not used for the called swap.
- It is no problem to define a fully-specialized std template for a user, but never try to add new things like std.