Extract parameter-independent code from the template

Source: Internet
Author: User
The Code irrelevant to parameters is separated from the template-Linux general technology-Linux programming and kernel information. The following is a detailed description. Templates is an excellent way to save time and avoid code duplication. You do not need to enter 20 similar classes. Each class contains 15 member functions. You can enter a class template ), and let the compiler instance produce the 20 specific classes (specific classes) and 300 functions you need. (The member functions (member function) of a class template is implicitly instantiated only when it is used. Therefore, only when every function is actually used, you will get all 300 member functions (member functions ).) Function templates has similar charm. You don't have to write a lot of functions. You can write a function templates (function template) and let the compiler do the rest. Isn't that an important technology?

Yes, good ...... Sometimes. If you are not careful, using templates (templates) may lead to code bloat (code expansion): repeated (or almost repeated) code, data, or binary code that both have. The results make the source code look compact and neat, but the target code is bloated and loose. Bloated and loose will rarely become fashionable, so you need to know how to avoid such binary expansion.

Your main tool has an imposing name: commonality and variability analysis (versatility and variability analysis), but there is nothing imposing on this idea. Even if you have never used a template in your career, you should perform this analysis from workshop.

When you write a function and realize that some parts of the function are essentially the same as the implementation of another function, will you just copy the code? Of course not. You can separate common code from these two functions and put them in the third function. Then, let the two functions call this new function. That is to say, you analyze the two functions to find the General and changed components. You move the general components into a new function and keep the changed components in the original function. Similarly, if you write a class and you realize that some components of this class are the same as those of another class, do not copy those general components. As an alternative, you move the general component into a new class, And then you use inheritance (inheritance) or composition (composite) to allow the original classes to access these general features. Different components in the original classes-changed components-remain in their original positions.

When writing templates (templates), you need to perform the same analysis and avoid repetition in the same way, but here is a trick. In non-template code, repetition is explicit: You can see that there are duplicates between two functions or two classes. In template code. Repetition is implicit: there is only one copy of the template source code. Therefore, you must train yourself to determine the possible repetition after a template is instantiated multiple times.

For example, if you want to write a templates template for square matrices (square matrix) of a fixed size, the template must support matrix inversion (matrix transpose ).

Template
Std: size_t n> // objects of type T; see below for info
Class SquareMatrix {// on the size_t parameter
Public:
...
Void invert (); // invert the matrix in place
};

This template gets a type parameter (type parameter) T, but it also has a parameter of type size_t-a non-type parameter (non-type parameter ). Non-type parameter (non-type parameter) is less generic than type parameter (type parameter), but they are completely legal and, as in this example, they can be very natural.

Consider the following code:

SquareMatrix sm1;
...
Sm1.invert (); // call SquareMatrix: invert

SquareMatrix sm2;
...
Sm2.invert (); // call SquareMatrix: invert

Two invert copies will be instantiated here. These two functions are not the same, because one acts on the 5x5 matrix, and the other acts on the 10x10 matrix, but except for the constants 5 and 10, these two functions are the same. This is a classic method of template-induced code bloat (template-caused code expansion.

If you see that the two functions use 5 and the other 10, all the corresponding characters are equal, what should you do? Your intuition allows you to create a function version that gets a value as a parameter, and then call this parameterized function with 5 or 10 to replace the copied code. Your intuition provides you with a good way! The following is a preliminary example of SquareMatrix:

Template // size-independent base class
Class SquareMatrixBase {// square matrices
Protected:
...
Void invert (std: size_t matrixSize); // invert matrix of the given size
...
};

Template <typename T, std: size_t n>
Class SquareMatrix: private SquareMatrixBase {
Private:
Using SquareMatrixBase: invert; // avoid hiding base version
// Invert; see Item 33
Public:
...
Void invert () {this-> invert (n);} // make inline call to base class
}; // Version of invert; see below
// For why "this->" is here

As you can see, the parameterized version of invert is in a base class (base class) SquareMatrixBase. Like SquareMatrix, SquareMatrixBase is a template, but unlike SquareMatrix, SquareMatrix only parameterized the object type in the matrix without the matrix size. Therefore, all matrices that hold a given object type will share a single SquareMatrixBase class. Thus, they share a single copy of the invert version in that class.

SquareMatrixBase: invert is just a method that is planned to be used for derived classes (derived classes) to avoid code duplication, so it is protected rather than public. The additional cost of calling it should be zero, because the inverts of the derived classes (derived class) calls the base class (base class) version using inline functions (inline function. (This inline is implicit-See Understanding inline-based intervention and exclusion.) These functions use the "this->" mark, because, as explained in Item 43, if not, the function names (such as SquareMatrixBase) in the templatized base classes (templated base class) hidden by derived classes (derived class. Note that the inheritance relationship between SquareMatrix and SquareMatrixBase is private. This accurately reflects the fact that base class (base class) only simplifies the implementation of derived classes (derived class, instead of representing a conceptual is-a relationship between SquareMatrixBase and SquareMatrixBase. (For information about private inheritance (private inheritance), see exercise caution when using private inheritance.)

So far, it's good, but we haven't mentioned a tough problem. SquareMatrixBase: How does invert know what data to operate on? It knows the size of a matrix from its parameters, but how does it know where the data of a specific matrix is? Only the derived class (derived class) knows this. How can derived class (derived class) convey these information to base class (base class) so that base class (base class) can perform this transpose?

One possibility is to add another parameter for SquareMatrixBase: invert, or a pointer to the starting position of the memory block storing matrix data. This can work, but in, invert is not the only function in SquareMatrix that can be written into a size-independent (size-independent) mode and moved into SquareMatrixBase. If there are several such functions, all need a method to find the memory holding the values in the matrix. We can add an additional parameter for all of them, but we repeatedly tell SquareMatrixBase the same information. This seems abnormal.

An alternative solution is to let SquareMatrixBase store a pointer to the memory area of the matrix value. And once it stores this pointer, it can also store the matrix size. The final design is roughly like this:

Template
Class SquareMatrixBase {
Protected:
SquareMatrixBase (std: size_t n, T * pMem) // store matrix size and
: Size (n), pData (pMem) {}// ptr to matrix values

Void setDataPtr (T * ptr) {pData = ptr;} // reassign pData
...

Private:
Std: size_t size; // size of matrix
T * pData; // pointer to matrix values
};

In this way, let the derived classes (derived class) decide how to allocate memory. Some implementations may decide to store matrix data directly in SquareMatrix object:

Template
Class SquareMatrix: private SquareMatrixBase {
Public:
SquareMatrix () // send matrix size and
: SquareMatrixBase (n, data) {}// data ptr to base class
...

Private:
T data [n * n];
};

This type of objects does not require dynamic memory allocation (dynamic memory allocation), but these objects may be very large. An optional solution is to place the data of each matrix on the heap (heap:

Template
Class SquareMatrix: private SquareMatrixBase {
Public:
SquareMatrix () // set base class data ptr to null,
: SquareMatrixBase (n, 0), // allocate memory for matrix
PData (new T [n * n]) // values, save a ptr to
{This-> setDataPtr (pData. get ();} // memory, and give a copy of it
... // To the base class

Private:
Boost: scoped_array pData; // see Item 13 for info on
}; // Boost: scoped_array

Regardless of where the data is stored, the key result from the expanded perspective is: Many of SquareMatrix's -- maybe all -- member functions (member functions) you can simply call the base class versions (base class version) of inline, which is shared with all other matrices that hold the same data type, regardless of their size. At the same time, SquareMatrix objects of different sizes are of different types. Therefore, even if SquareMatrix and SquareMatrix objects use the same member functions (member function) in SquareMatrixBase ), there is no chance to transmit a SquareMatrix object to a function that expects a SquareMatrix. Good, isn't it?

Good, yes, but not free. The version of the invert in which the matrix size is hard and fixed is likely to generate better code than passing the size as a function parameter or a shared version stored in the object. For example, in a version with a specific size, sizes (size) will become compile-time constants (compile-time constant), so it is suitable for optimization like constant propagation, these include embedding them into the generated command as immediate operands (immediate operand. This is not possible in size-independent version.

On the other hand, the unique invert version is used for multiple matrix sizes to reduce the executable code size, and the working set (workspace) Size of the program can also be reduced and the instruction cache can be improved) locality of reference in ). These can make the program run faster and overpay any optimization of the lost size-specific versions (specific size version) for invert. Which one is more cost-effective? The only resolution method is to test two methods on your specific platform and typical datasets and observe their behavior.

Another efficiency concerns the size of objects. If you are not careful, moving the size-independent version of the function (size-independent version) to a base class will increase the overall size of each object. For example, in the code I just showed, even if every derived class (derived class) has a way to get data, each SquareMatrix object has a pointer to its data stored in SquareMatrixBase class, which increases the size of each SquareMatrix object by at least one pointer. It is possible to change the design so that these pointers are no longer necessary, but this is another transaction. For example, a base class stores a protected pointer pointing to matrix data, reducing encapsulation. It may also complicate Resource Management: if the base class stores a pointer to the matrix data, however, the data can be dynamically allocated or physically stored in the derived class object (derived class object) (as we can see ), how does it determine whether the pointer should be deleted? There are answers to these questions, but the more you want to make them more sophisticated, the more complicated it will become. Under some conditions, a small amount of code repetition is like a relief.

This article only discusses the expansion caused by non-type template parameters (non-type template parameters), but type parameters (type parameters) can also lead to expansion. For example, in many platforms, int and long have the same binary representation. Therefore, we can say that member functions (member function) of vector and vector) it is likely to be the same-the just-Right Explanation of expansion. Some connection programs merge the same function implementation, and some do not, which means that some templates in some environments are instantiated on int and long, which can lead to code duplication. Similarly, on most platforms, all pointer types have the same binary representation, so they hold pointer-type templates (such as list, list, list *>) generally, you can use a single underlying implementation of every member function (member function. In typical cases, this means the member functions (member function) that works with stronugly typed pointers (strong type pointer) (that is, T * pointer) you can call the functions that work with the untyped pointers (no type pointer) (that is, the void * pointer. Some Standard C ++ libraries are implemented for templates such as vector, deque, and list. If you care about the code expansion caused by your template, you may need to develop the template using the same method.
Things to Remember
Templates (template) generates multiple classes and multiple functions, so some template code that does not depend on the template parameter (template parameter) will cause expansion.
The expansion caused by non-type template parameters (non-type template parameters) can often be replaced by the function parameters (function parameter) or class data members (class data member) template parameters (template parameter) and eliminate.
The expansion caused by type parameters can be reduced by sharing the instantiation types with the same binary representation.

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.