• Effective C++ 笔记 —— Item 44: Factor parameterindependent code out of templates.


    For example, suppose you'd like to write a template for fixed-size square matrices that, among other things, support matrix inversion.

    template<typename T, std::size_t n> // template for n x n matrices of objects of type T; see below for info on the size_t parameter
    class SquareMatrix 
    {
    public:
        // ...
        void invert(); // invert the matrix in place
    };
    
    SquareMatrix<double, 5> sm1;
    // ...
    sm1.invert(); // call SquareMatrix<double, 5>::invert
    
    SquareMatrix<double, 10> sm2;
    // ...
    sm2.invert(); // call SquareMatrix<double, 10>::invert

    Two copies of invert will be instantiated here. The functions won't be identical, because one will work on 5 ×5 matrices and one will work on 10 ×10 matrices, but other than the constants 5 and 10, the two functions will be the same. This is a classic way for template-induced code bloat to arise.

    Here's a first pass at doing that for SquareMatrix:

    template<typename T>    // size-independent base class for square matrices
    class SquareMatrixBase
    { 
    protected:
        //...
        void invert(std::size_t matrixSize); // invert matrix of the given size
        //...
    };
    
    template<typename T, std::size_t n>
    class SquareMatrix : private SquareMatrixBase<T> 
    {
    private:
        using SquareMatrixBase<T>::invert; // make base class version of invert visible in this class; see Items 33 and 43
    
    public:
        //...
        void invert() { invert(n); } // make inline call to base class
    };

    All matrices holding a given type of object will share a single SquareMatrixBase class. They will thus share a single copy of that class's version of invert.

    • SquareMatrixBase::invert is intended only to be a way for derived classes to avoid code replication, so it's protected instead of being public.
    • The additional cost of calling it should be zero, because derived classes' inverts call the base class version using inline functions. (The inline is implicit — see Item 30.)
    • Notice also that the inheritance between SquareMatrix and SquareMatrixBase is private. This accurately reflects the fact that the reason for the base class is only to facilitate the derived classes' implementations, not to express a conceptual is-a relationship between SquareMatrix and SquareMatrixBase. (For information on private inheritance, see Item 39)

    So far, so good, but there's a sticky issue we haven't addressed yet. How does SquareMatrixBase::invert know what data to operate on?

    Have SquareMatrixBase store a pointer to the memory for the matrix values. And as long as it's storing that, it might as well store the matrix size, too. The resulting design looks like this:

    template<typename T>
    class SquareMatrixBase 
    {
    protected:
        SquareMatrixBase(std::size_t n, T *pMem) // store matrix size and a ptr to matrix values
            : size(n), pData(pMem) {}
    
        void setDataPtr(T *ptr) { pData = ptr; } // reassign pData
        //...
    
    private:
        std::size_t size;    // size of matrix
        T *pData;            // pointer to matrix values
    };

    This lets derived classes decide how to allocate the memory. Some implementations might decide to store the matrix data right inside the SquareMatrix object:

    template<typename T, std::size_t n>
    class SquareMatrix : private SquareMatrixBase<T> 
    {
    public:
        SquareMatrix() // send matrix size and 
            : SquareMatrixBase<T>(n, data) {} // data ptr to base class
        //...
    
    private:
        T data[n*n];
    };

    Objects of such types have no need for dynamic memory allocation, but the objects themselves could be very large. An alternative would be to put the data for each matrix on the heap

    template<typename T, std::size_t n>
    class SquareMatrix : private SquareMatrixBase<T> 
    {
    public:
        SquareMatrix() // set base class data ptr to null,
            : SquareMatrixBase<T>(n, 0), // allocate memory for matrix values, save a ptr to the memory, and give a copy of it to the base class
            pData(new T[n*n]) //
        {
            this->setDataPtr(pData.get());
        } 
        // ...
    
    private:
        boost::scoped_array<T> pData; // see Item 13 for info on boost::scoped_array
    };

    This Item has discussed only bloat due to non-type template parameters, but type parameters can lead to bloat, too. For example, on many platforms, int and long have the same binary representation, so the member functions for, say, vector and vector would likely be identical — the very definition of bloat. Some linkers will merge identical function implementations, but some will not, and that means that some templates instantiated on both int and long could cause code bloat in some environments. Similarly, on most platforms, all pointer types have the same binary representation, so templates holding pointer types (e.g., list<int*>, list, list<squarematrix<long, 3="">*>, etc.) should often be able to use a single underlying implementation for each member function. Typically, this means implementing member functions that work with strongly typed pointers (i.e., T* pointers) by having them call functions that work with untyped pointers (i.e., void* pointers). Some implementations of the standard C++ library do this for templates like vector, deque, and list. If you're concerned about code bloat arising in your templates, you'll probably want to develop templates that do the same thing.

    Things to Remember

    • Templates generate multiple classes and multiple functions, so any template code not dependent on a template parameter causes bloat.
    • Bloat due to non-type template parameters can often be eliminated by replacing template parameters with function parameters or class data members.
    • Bloat due to type parameters can be reduced by sharing implementations for instantiation types with identical binary representations
  • 相关阅读:
    泛型接口协变和抗变
    泛型类功能
    泛型结构
    using 关键字给类和名称空间指定别名
    sqlite创建数据库问题
    sqlite命令
    必须输入大于0的整数
    最近在看c#本质论和B站上对应这本书的视频
    Linux系统管理笔记
    创建圆形类,其中包括set,get方法
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15949321.html
Copyright © 2020-2023  润新知