Learn C++学习笔记:第十二章—模板template类和函数的特化(定制)

一、函数模板定制

函数模板可以实现对于不同的类型复用同一套代码,但是某些情况下,某些类型需要专门去实现,不能套用模板,举例比如对于print函数,可以打印整型、浮点型甚至字符,但是如果输入一个指针呢?所以这个时候就需要对指针类型的这个函数进行专门定制。
假设是下面这个类:

template <typename T>
class Storage{
private: T m_value;
public:
	Storage(T value){m_value = value;}
	~Storage(){}
	void print(){
	std::cout << m_value<<'\n';
	}
}

这个模板类对很多类型都适用,比如:

Storage<int> nValue(5);
Storage<double> dValue(6.7);
nValue.print();
dValue.print();

假如我们没有进行模板特化,传入一个指针类型会发生什么?如下面代码所示

const char *string = new char[40];
string = "abcde";
Storage<const char*> storage(string);
delete[] string;
string.print();

如果中间没有那一句delete[] string则程序会正常输出,但是上面的代码却在vs里面直接报错。因为这里面在类初始化中,构造函数如下:

template <>
Storage<char*>::Storage(char* value)
{
     m_value = value;
}

这里面只做了一个浅拷贝,当把外部的变量删了之后,这个自然就找不到位置了。所以需要针对某些特定类型做模板的定制 。

具体规则就是在要具体定义的函数前面加上template <>。因为这里就是具体规定类型,所以就用空<>代替即可。

template <>
Storage<char*>::Storage(char* value)
{
    // Figure out how long the string in value is
    int length=0;
    while (value[length] != '\0')
        ++length;
    ++length; // +1 to account for null terminator
 
    // Allocate memory to hold the value string
    m_value = new char[length];
 
    // Copy the actual value string into the m_value memory we just allocated
    for (int count=0; count < length; ++count)
        m_value[count] = value[count];
}

具体的函数专业化其实是已经结束了,但是作为代码的完整性,还不够,因为上面内部开辟了一块内存空间,但是,没有释放这一块内存。所以完整代码还需要补充一块析构函数。

template <>
Storage<char*>::~Storage()
{
    delete[] m_value;
}

二、类模板定制

大体思路和函数模板定制接近,教程选了一个比较难的例子。

template <class T>
class Storage8
{
private:
    T m_array[8];
 
public:
    void set(int index, const T &value)
    {
        m_array[index] = value;
    }
 
    const T& get(int index) const
    {
        return m_array[index];
    }
};

对于上面的例子,初始化类为Storage8<int> intStorage什么的都可以,但是如果初始化为一个Storage8<bool> boolStorage,一个变量都至少需要一个字节,所以本来只需要一个字节来存储的,但是目前需要8个字节,所以对于类型为bool的类,为了提高空间利用效率,可以针对这个类型专门定制。
那为什么不重新定义一个类呢?因为我们必须给它起一个不同的名字。然后,程序员必须记住,Storage8 是用于非布尔类型的,而Storage8Bool(或我们称之为新类的任何名称)是布尔的。我们宁愿避免这种不必要的复杂性。
规则:
首先,请注意,我们从开始template<>。template关键字告诉编译器以下内容已模板化,而空括号则表示没有任何模板参数。在这种情况下,没有任何模板参数,因为我们将唯一的模板参数(T)替换为特定类型(bool)。
接下来,我们在类名称中添加以表示我们正在对Storage8类的bool版本进行专用化。

template <> // the following is a template class with no templated parameters
class Storage8<bool> // we're specializing Storage8 for bool
{
// What follows is just standard class implementation details
private:
    unsigned char m_data{};
 
public:
    void set(int index, bool value)
    {
        // Figure out which bit we're setting/unsetting
        // This will put a 1 in the bit we're interested in turning on/off
        auto mask{ 1 << index };
 
        if (value)  // If we're setting a bit
            m_data |= mask;  // Use bitwise-or to turn that bit on
        else  // if we're turning a bit off
            m_data &= ~mask;  // bitwise-and the inverse mask to turn that bit off
	}
	
    bool get(int index)
    {
        // Figure out which bit we're getting
        auto mask{ 1 << index };
        // bitwise-and to get the value of the bit we're interested in
        // Then implicit cast to boolean
        return (m_data & mask);
    }
};

三、部分模板定制

3.1 单独的函数部分模板定制

上面两部分的专业化其实是模板的完全定制,就是所有的参数都确定下后进行定义,但是对于含有非类型参数的类型:

template <class T, int size> // size is the expression parameter
class StaticArray{......}

对于这种模板,可能有时候需要定制的是第一个参数,第二个参数个数不能被定制。这种定制被称为部分模板定制化
举例一个打印上述类内容的函数:

template <typename T, int size>
void print(StaticArray<T, size> &array)
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count] << ' ';
}

假设我们需要对StaticArray类中char类型进行print函数定制的话?怎么写呢?首先因为typename T已经被固定了,所以首行中的template中不需要在进行类型定义了,只需要保留第二个参数,定义即:

template <typename T, int size>
void print(StaticArray<T, size> &array)
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count] << ' ';
}
 
// overload of print() function for partially specialized StaticArray<char, size>
template <int size>                             //不能定制的参数,这里要保留
void print(StaticArray<char, size> &array)      //注意这里面Staticarray中的类型要明确
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count];
}

3.2 类的函数部分定制

如果我们要对一个类中的某些函数进行部分定制怎么办呢?
如下面这个类中的print函数。

template <class T, int size> // size is the expression parameter
class StaticArray
{
private:  T m_array[size]{};
public:
    T* getArray() { return m_array; }
	
    T& operator[](int index){ return m_array[index];}
 
    void print(){
        for (int i{ 0 }; i < size; ++i)
            std::cout << m_array[i] << ' ';
        std::cout << '\n';
    }
};

最直观的想法就是这样写:

// Error!!!!
template <int size>
void StaticArray<double, size>::print()
{
	for (int i{ 0 }; i < size; ++i)
		std::cout << std::scientific << m_array[i] << ' ';
	std::cout << '\n';
}

然而这样写是错误的。其实也可以直接重新定义这个类:

template <int size>
void StaticArray<double, size>
{......}

但是由于我们只需要重写print函数,所以这样写就会有很多重复的代码。
解决这个问题其实没有进一步的语法可以规避,靠的是一个trick
我们可以定义一个基础类:

template <class T, int size> // size is the expression parameter
class StaticArray_Base
{......}

然后一般的类都这样用:

template <class T, int size> // size is the expression parameter
class StaticArray: public StaticArray_Base<T, size>
{
public:
};

对于要重写的类,可以这么写:

template <int size> // size is the expression parameter
class StaticArray<double, size>: public StaticArray_Base<double, size>
{
public:
 
	void print()
	{
		for (int i{ 0 }; i < size; ++i)
			std::cout << std::scientific << this->m_array[i] << ' ';
// note: The this-> prefix in the above line is needed.
// See https://stackoverflow.com/a/6592617 or https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-members for more info on why.
		std::cout << '\n';
	}
};

3.3 指针相关的部分模板专门化

重温一下前面使用过的一个例子:

template <class T>
class Storage
{
private:
    T m_value;
public:
    Storage(T value)
    {
         m_value = value;
    }
 
    ~Storage()
    {
    }
 
    void print()
    {
        std::cout << m_value << '\n';
    }
};

前面的例子中,我们定制化了char*这个类型。但是问题其实还有:如果是int* float*……这些指针怎么办呢?
int* float*这些指针明显又有很多相通的地方,所以指针很多类型其实又可以规划为一大类,进行指针类型定制。如下:

template <typename T>
class Storage<T*> // this is a partial-specialization of Storage that works with pointer types
{
private:
    T* m_value;
public:
    Storage(T* value) // for pointer type T
    {
         // For pointers, we'll do a deep copy
         m_value = new T(*value); // this copies a single value, not an array
    }
 
    ~Storage()
    {
        delete m_value; // so we use scalar delete here, not array delete
    }
 
    void print()
    {
        std::cout << *m_value << '\n';
    }
};

值得注意的是,由于此部分专用的Storage类仅分配一个值,因此对于C样式的字符串,将仅复制第一个字符。如果希望复制整个字符串,则可以完全专门化char *类型的构造函数(和析构函数)。完全专业的版本将优先于部分专业的版本。这是一个示例程序,它既使用指针的部分专门化,又使用char *的完全专门化:
完全体代码:

#include <iostream>
#include <cstring>
 
// Our Storage class for non-pointers
template <class T>
class Storage
{
private:
	T m_value;
public:
	Storage(T value)
	{
		m_value = value;
	}
 
	~Storage()
	{
	}
 
	void print()
	{
		std::cout << m_value << '\n';
	}
};
 
// Partial-specialization of Storage class for pointers
template <class T>
class Storage<T*>
{
private:
	T* m_value;
public:
	Storage(T* value)
	{
		m_value = new T(*value);
	}
 
	~Storage()
	{
		delete m_value;
	}
 
	void print()
	{
		std::cout << *m_value << '\n';
	}
};
 
// Full specialization of constructor for type char*
template <>
Storage<char*>::Storage(char* value)
{
	// Figure out how long the string in value is
	int length = 0;
	while (value[length] != '\0')
		++length;
	++length; // +1 to account for null terminator
 
	// Allocate memory to hold the value string
	m_value = new char[length];
 
	// Copy the actual value string into the m_value memory we just allocated
	for (int count = 0; count < length; ++count)
		m_value[count] = value[count];
}
 
// Full specialization of destructor for type char*
template<>
Storage<char*>::~Storage()
{
	delete[] m_value;
}
 
// Full specialization of print function for type char*
// Without this, printing a Storage<char*> would call Storage<T*>::print(), which only prints the first element
template<>
void Storage<char*>::print()
{
	std::cout << m_value;
}
 
int main()
{
	// Declare a non-pointer Storage to show it works
	Storage<int> myint(5);
	myint.print();
 
	// Declare a pointer Storage to show it works
	int x = 7;
	Storage<int*> myintptr(&x);
 
	// If myintptr did a pointer assignment on x,
	// then changing x will change myintptr too
	x = 9;
	myintptr.print();
 
	// Dynamically allocate a temporary string
	char *name = new char[40]{ "Alex" }; // requires C++14
 
	// If your compiler isn't C++14 compatible, comment out the above line and uncomment these
//	char *name = new char[40];
//	strcpy(name, "Alex");
 
	// Store the name
	Storage< char*> myname(name);
 
	// Delete the temporary string
	delete[] name;
 
	// Print out our name
	myname.print();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值