Reading Notes Objective c ++ Item 44 extracts code irrelevant to template parameters and implements tiveitem

Source: Internet
Author: User

Reading Notes Objective c ++ Item 44 extracts code irrelevant to template parameters and implements tiveitem
1. using a template may result in code Expansion

Using templates is a good way to save time and avoid code reuse. You do not need to manually enter 20 identical class names. Each class has 15 member functions. Instead, you only need to enter a class template, then let the compiler instantiate 20 specific classes and 300 functions you need for you. (The member functions of the class template are implicitly instantiated only when they are used, therefore, 300 member functions are generated only when 300 functions are actually used .) Function templates are equally attractive. You don't need to implement many functions manually. You only need to implement a function template and then let the compiler do the rest.

However, sometimes, if you are not careful, using a template will lead to code bloat: a binary file that generates duplicate code or data, or both. The result may be that the source code looks neat, but the target code is bloated and relaxed. It's not good to be bloated and relaxed, so you need to know if you want to avoid such binary exaggeration.

2. Analysis of commonalities and Variability

Your main tool has the awesome name: commonality and variability analysis, but this concept is common. Even if you have never implemented a template in your programming career, you will always do this analysis.

2.1 repeated code analysis in functions and Classes

When you are implementing a function, you realize that some parts of the function implementation are basically the same as the implementation of another function. Will you repeat the code? Of course not. You extract the public code of the two functions, put them into the third function, and then call the new function in the two functions. To sum up, you can analyze the two functions, find the same and different parts, and move the same part to a new function, save different parts in the original function. Similarly, if you are implementing a class, you realize that one part of the class is the same in another class, and you should not rewrite the same part. Instead, you can move the same part to a new class, and then use inheritance or combination (Item 32, Item 38, Item 39) to allow the original class to access common features. Different parts of the original class are retained in the original position.

2.2 repeated code analysis and elimination in the template

When implementing the template, you will also perform the same analysis, and you will use the same method to prevent duplication, but here is a place that hurts you. In non-template code, the code is repeatedly displayed: You can see Code duplication between functions or between classes. In the template code, repetition is implicit: there is only one template source code,Therefore, you must train yourself. When a template is instantiated multiple times, you can feel whether repetition will happen..

2.2.1 eliminate code expansion first-remove non-type parameters

For example, if you want to implement a template for a fixed-size matrix, you need to support matrix transpose.

1 template<typename T, // template for n x n matrices of2 std::size_t n> // objects of type T; see below for info3 class SquareMatrix { // on the size_t parameter4 public:5 ...6 7 void invert();                         // invert the matrix in place8 9 };     

This template includes a type parameter T, but also a size_t parameter and a non-type parameter. Non-type parameters are less common than type parameters, but they are completely legal, and they are also very natural in this example.

Consider the following code:

 1 SquareMatrix<double, 5> sm1; 2  3 ... 4  5 sm1.invert(); 6  7 // call SquareMatrix<double, 5>::invert 8  9 SquareMatrix<double, 10> sm2;10 11  12 13 ...14 15  16 17 sm2.invert();18 19 // call SquareMatrix<double, 10>::invert20 21  

 

Here, two copies of invert will be instantiated. These two functions are not the same, because one works on the matrix of 5*5 and the other works on the matrix of 10*10, but if constants 5 and 10 are not considered, these two functions will be the same. This is a typical way to expand the code containing the template.

If you see two functions, all their characters are the same, except that one version uses 5 and the other version uses 10, what will you do next? Your intuition is that you will create a function version with a parameter, and then call this function with the input parameter 5 or 10 instead of repeating the code. Your intuition can serve you well! This is the first level to implement SquareMatrix:

 1 template<typename T> // size-independent base class for 2 class SquareMatrixBase { // square matrices 3 protected: 4 ... 5 void invert(std::size_t matrixSize); // invert matrix of the given size 6 ... 7 }; 8 template<typename T, std::size_t n> 9 class SquareMatrix: private SquareMatrixBase<T> {10 private:11 using SquareMatrixBase<T>::invert; // make base class version of invert12 // visible in this class; see Items 3313 // and Item 4314 public:15 ...16 void invert() { invert(n); } // make inline call to base class17 }; // version of invert

 

As you can see, the invert version with parameters is placed in the base class SquareMatrixBase. Like SquareMatrix, SquareMatrixBase is a template, but unlike SquareMatrix, it only templates object types in the matrix. Therefore, all matrices containing an object of a given type will share a single SquareMatrixBase class. In this way, they will share a single copy of the invert version of The SquareMatrixBase class. (You cannot declare it as inline, because once it is inline, every SquareMatrix: invert instance will get a copy of SquareMatrixBase: invert code (see Item 30 ), you will find that you have returned to the origin of repeated object code .)

SquareMatrixBase: invert is only used to prevent code duplication in the derived class, so it is protected rather than public. The additional overhead for calling it should be 0, because the inverts of the derived class calls the base class version using the inline function. (Inline is implicit, see Item 30) at the same time, note that the inheritance between SquareMatrix and SquareMarixBase is private. This accurately reflects the fact:The only reason for using the base class is to help the implementation of the derived class, not to expressSquareMatrixAndSquareMatrixBaseBetweenIs-"Relationship. (For details about private inheritance, see Item 39)

2.2.2 eliminate code expansion Level 2-how does a derived class tell the base class where data is located

It seems to be good till now, but there is another tricky problem we have not dealt. SquareMatrixBase: invert, how do I know what data to operate on? It knows the size of the rectangle from the parameter, but how does it know where the data provided for the special matrix is? Only the derived classes will know. How can a derived class communicate with a base class to allow the base class to execute invert?

One possible method is to add another parameter to SquareMatrixBase: invert, which may be a pointer to a piece of memory that stores rectangular data. This method can work, but it is always effective.NoWhich can be rewritten independently of the size in SquareMatrix and moved into SquareMatrixBaseUnique Function. If there are several such functions, we need a way to find the memory for storing the rectangular data. We can add an additional parameter for all the functions, however, we have repeatedly told SquareMatrixBase the same information. This seems to be wrong.

One replacement method is to let SquareMatrixBase store a pointer to the memory where the rectangular data is stored. This has the same effect as storing the rectangle size. The result is as follows:

 1 template<typename T> 2 class SquareMatrixBase { 3 protected: 4 SquareMatrixBase(std::size_t n, T *pMem) // store matrix size and a 5 : size(n), pData(pMem) {} // ptr to matrix values 6  7 void setDataPtr(T *ptr) { pData = ptr; }   // reassign pData 8  9 ...                                                            10 11 private:                                                  12 13  14 15 std::size_t size;           // size of matrix16 17 T *pData;       // pointer to matrix values18 19 20 };

 

This allows the derived class to determine how to allocate memory. Some implementations store rectangular data in the SquareMatrix object:

1 template<typename T, std::size_t n>2 class SquareMatrix: private SquareMatrixBase<T> {3 public:4 SquareMatrix() // send matrix size and5 : SquareMatrixBase<T>(n, data) {} // data ptr to base class6 ...7 private:8 T data[n*n];9 };

 

There is no need to allocate dynamic memory for this type of object, but the object itself may be very large. One replacement method is to store data on the stack for each rectangle:

 1 template<typename T, std::size_t n> 2 class SquareMatrix: private SquareMatrixBase<T> { 3 public: 4 SquareMatrix() // set base class data ptr to null, 5 : SquareMatrixBase<T>(n, 0), // allocate memory for matrix 6 pData(new T[n*n]) // values, save a ptr to the 7 { this->setDataPtr(pData.get()); } // memory, and give a copy of it 8  9 ...                                              // to the base class10 11 private:12 boost::scoped_array<T> pData;          // see Item 13 for info on13 14  15 16 };                                   // boost::scoped_array

 

2.2.3 efficiency comparison before and after code expansion Elimination

No matter where the data is stored, the key result of code expansion isNow there are many (maybe all)SquareMatrix member functions can be used to call the non-inline function version of the base class in simple inline. All the functions in the base class with the same data type are shared by rectangles, regardless of the size.. At the same time, SquareMatrix objects of different sizes belong to different types, so even if SquareMatrix <double, 5> and SquareMatrix <double, 10> objects use the same member function in SquareMatrixBase <double>, it is impossible to pass a SquareMatrix <double, 5> object to a function that requires SquareMatrix <double, 10>. Good or bad.

Good is good, but it requires a price. An invert version with a fixed rectangle size may produce better code than an invert version with a fixed size passed by function parameters (or stored in an object. For example, in a version with a specified size, sizes is a constant during the compilation period. Therefore, sizes is a qualified constant propagation optimization. You can also put it into the generating command as a direct operand. This cannot be done in versions unrelated to the same size.

In addition, providing only one invert version for matrices of different sizes can reduce the size of the executable program, which can reduce the working set size of the program, in addition, it can enhance the centralized reference of instruction high-speed cache. These things can make the program run faster, and can be optimized relative to the version specified by size, it may make better compensation. Which method is better? The only way is to try both methods and observe their behavior on your specific platform and representative datasets.

Another consideration for efficiency is the size of related objects. If you don't mind, moving a version irrelevant to the size to the base class will increase the size of each object. For example, in the code I just showed, each SquareMatrix object has a pointer to the data in the SquareMatrixBase class. Even if each derived class already has a method for getting data, this also adds at least one pointer size for each SquareMatrix object. We can modify the design to remove pointers, but this also requires a price. For example, letting the base class store a protected pointer to the data will lead to a reduction in encapsulation (Item 22 ). it can also cause resource management complications: if the base class stores pointers to matrix data, however, data may be dynamically allocated or stored in a derived class Object (as we can see). How can we determine whether the delete pointer is required? There are answers to these questions, but the finer you are doing, the more complex you are. In a sense, a bit of code is a bit lucky to start opening again.

2.3 How to Deal with code expansion caused by type template parameters

This article only discusses the code expansion caused by non-type template parameters, but the type parameters can also lead to code expansion. For example, in many platforms, int and long have the same binary representation, so the use of vector <int> and vector <long> in member functions looks the same, this is the definition of code expansion. Some connectors integrate the same code implementation, but some do not. This means that the int and long versions instantiated by the template will cause code expansion in some environments. Similarly, on most platforms, all pointer types share the same binary representation, so templates with pointer types (for example, list <int *>, list <const *>, list <SquareMatrix <long, 3> *> and so on) should be able to use a single underlying implementation for each member function. Specifically, this means that when a member function with a strong type pointer (T * pointer) is implemented, they are asked to call a function without a type pointer (void * pointer ). Some Standard C ++ libraries are implemented as templates (such as vector, deque, and list ). If you are concerned about the code expansion problems in your template, you may want to develop a template to do the same thing.

3. Summary
  • The template generates multiple classes and multiple functions, so any template should not depend on the template parameters that will cause code expansion.
  • Non-type template parameters may cause code expansion.Replace with function parameters or class data membersTo clear.
  • Code expansion caused by type parameters can also be reduced by sharing the same binary representation for the instantiation type.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.