Item 44: refactor the parameter-independent code outside the Template
Item 44: Factor parameter-independent code out of templates.
Template is a good thing. You can write less Code while implementing type security. However, the template provides polymorphism during the compilation period. Even if your code looks very concise and short, the generated binary file may contain a large amount of redundant code. Because the template generates a complete copy each time it is instantiated, the code bloat is caused by the code that is irrelevant to the template parameters ).
Refactoring the code irrelevant to the parameters in the template outside the template can effectively control the code expansion generated by the template. In addition, code expansion can also be generated by type template parameters:
- For code expansion caused by non-type template parameters, use function parameters or data members instead of template parameters to eliminate redundancy;
- For the code expansion generated by the type template parameters, different instantiated template classes can share the same binary representation. Extract public code
To avoid code redundancy, we use the commonality and variability analysis Method every day. When you write several functions, the public part is extracted to another function. When you declare a class, the public part is also extracted to the parent class.
So you want to use this method in template programming to avoid code duplication, but the template and non-template code are different in this regard:
- Explicit redundancy in non-template code ). You will see the code as long as there is repeated code;
- In Template code, redundancy is implicit (implicit ). There is only one template code, and the redundancy generated when the template is instantiated requires your intuition to feel it. Template-generated code Expansion
Now let's take a look at how a template causes code expansion. For example, to implement a fixed-size matrix, it supports transpose operations.
template
class Square{public: void invert();};
Theint n
Is a non-type parameter, it is also a valid template parameter ~
Then the template may be used as follows:
Square
s1;Square
s2;s1.invert(); s2.invert();
Square
The template instantiates two classes:Square
AndSquare
, They have the sameinvert
Method. This is a typical scenario for template Generation Code expansion.
Extract parent TemplateThe Code expansion produced by the ending template is still a method of extracting public code. If you see two identicalinvert
Function. Your intuition must be to extract it to another class:
template
class SquareBase{protected: void invert(int size);};template
class Square:private SquareBase
{private: using SquareBase
::invert;public: void invert(){ this->invert(n); }}
Becauseinvert
The function is defined in the base class, so it only appears once in binary code, that isSquareBase ::invert
. This function is shared by two sub-classes. Some details in the above Code are worth mentioning:
SquareBase::invert
Is used for subclass, so the declaration isprivate
Insteadpublic
;
- Call parent class
invert
The cost is zero becauseSquare::invert
Is an implicit inline function, see Item 30;
- Use
this->
The prefix is because,SquareBase
In the subclass TemplateSquare
Is hidden, see Item 43;
- Use
private
Inheritance is because,Square
Is implemented in termsSquare
, See Item 39. Data storage problemsSince we decided to do it by the parent classinvert
Operation. How does the parent class access data? Because the data is originally in the subclass. Of course, we can callSquareBase::invert
The memory address is also the parent class, But what if many functions in the matrix class need this information? We may need to pass this information to the parent class function when calling each function. In this case, why not place the data address directly in the parent class? Since the parent class stores data, we can store the matrix size together!
template
class SquareBase{protected: SquareBase(int _n, T *_data): n(_n), data(_data){} void setData(T *_data){ data = _data; }private: int n; T* data;};
The parent class stores the location of the matrix data (data
) And size (n
), Subclass can still decide how to allocate address space. It can be stored in the subclass as a member attribute or dynamically applied for memory.
Trade-offsNo matter how data is allocated and accessed, the solution for eliminating code duplication is fixed: extract the public part to the parent template class. This solution avoids code expansion, reduces the size of binary files and "working set", improves the hit rate of instruction cache, and achieves higher code execution efficiency. However, extracting the public part to the new template class also causes some problems:
- If
int n
The compiler can optimize hard encoding in template parameters, such as constant propagation. Howeverint n
As a function parameter, these optimizations are gone.
- Adding a class level will increase the object size. At least one
T* data
Pointer.In practice, whether or not public code should be extracted depends on your application scenario. weigh the advantages and disadvantages of the above.
This topic discusses non-type template parameters. For type template parameters, code expansion also exists, such
int
Andlong
Most platforms share the same underlying implementation, but templates are instantiated into two copies because they have different types.
List
,List
,List
The underlying implementation is the same. However, because the pointer types are different, they are also instantiated into multiple template classes.