指针的部分模板特化
在上一课 函数模板专业化中,我们看了一个简单的模板化Storage类:
#include <iostream>
template <class T>
class Storage
{
private:
T m_value;
public:
Storage(T value)
{
m_value = value;
}
~Storage()
{
}
void print()
{
std::cout << m_value << '\n';
}
};
我们发现当模板参数T的类型为char *时,这个类有问题,因为在构造函数中发生了浅的复制/指针赋值。在该课程中,我们使用完整的模板专门化为类型char *创建存储构造函数的专用版本,该类型分配了内存并创建了m_value的实际深层副本。作为参考,这里是完全专用的char * Storage构造函数和析构函数:
// 您需要在此处包含上面示例中的Storage <T>类
template <>
Storage<char*>::Storage(char* value)
{
//弄清楚字符串的值有多长
int length=0;
while (value[length] != '\0')
++length;
++length; // +1代表空终止符
// 分配内存以保存值字符串
m_value = new char[length];
//将实际值字符串复制到我们刚刚分配的m_value内存中
for (int count=0; count < length; ++count)
m_value[count] = value[count];
}
template<>
Storage<char*>::~Storage()
{
delete[] m_value;
}
虽然这对于Storage <char *>非常有用,但是其他指针类型(例如int *)呢?很容易看出,如果T是任何指针类型,那么我们会遇到构造函数执行指针赋值的问题,而不是对指向的元素进行实际的深层复制。
因为完整的模板特化迫使我们完全解析模板类型,为了解决这个问题,我们必须为我们想要使用存储的每个指针类型定义一个新的专用构造函数(和析构函数)!这导致了许多重复的代码,正如您现在所熟知的那样,我们希望尽可能避免这些代码。
幸运的是,部分模板专业化为我们提供了方便的解决方案。在这种情况下,我们将使用类部分模板特化来定义适用于指针值的特殊版本的Storage类。这个类被认为是部分专用的,因为我们告诉编译器它只用于指针类型,即使我们没有完全指定基础类型。
#include <iostream>
//您需要在此处包含上面示例中的Storage <T>类
template <typename T>
class Storage<T*> // 这是与指针类型一起使用的存储的部分特化
{
private:
T* m_value;
public:
Storage(T* value) // 对于指针类型T.
{
// 对于指针,我们将进行深层复制
m_value = new T(*value); //这会复制单个值,而不是数组
}
~Storage()
{
delete m_value; // 所以我们在这里使用删除数量,而不是删除数组
}
void print()
{
std::cout << *m_value << '\n';
}
};
这个工作的一个例子:
int main()
{
// 声明一个非指针存储以显示它的工作原理
Storage<int> myint(5);
myint.print();
// 声明指针存储以显示其工作原理
int x = 7;
Storage<int*> myintptr(&x);
//如果myintptr在x上执行指针赋值,
// 那么更改x也会更改myintptr
x = 9;
myintptr.print();
return 0;
}
这打印值:
5
7
当使用int * template参数定义myintptr时,编译器会看到我们已经定义了一个适用于任何指针类型的部分专用模板类,并实例化了一个版本的Storage使用该模板。该类的构造函数生成参数x的深层副本。稍后,当我们将x更改为9时,myintptr.m_value不会受到影响,因为它指向它自己的值的单独副本。
如果部分模板特化类不存在,myintptr将使用模板的正常(非部分专用)版本。该类的构造函数执行浅复制指针赋值,这意味着myintptr.m_value和x将引用相同的地址。然后,当我们将x的值更改为9时,我们也会改变myintptr的值。
值得注意的是,因为这个部分专用的Storage类只分配一个值,对于C风格的字符串,只会复制第一个字符。如果希望复制整个字符串,则char *类型的构造函数(和析构函数)的特化可以完全专门化。完全专业化的版本将优先于部分专用版本。这是一个示例程序,它既使用指针的部分特化,又使用char *的完全特化:
#include <iostream>
#include <cstring>
// 我们的非指针存储类
template <class T>
class Storage
{
private:
T m_value;
public:
Storage(T value)
{
m_value = value;
}
~Storage()
{
}
void print()
{
std::cout << m_value << '\n';
}
};
// 指针的存储类的部分特化
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';
}
};
// char*类型的构造函数的完全特化
template <>
Storage<char*>::Storage(char* value)
{
//计算字符串的值是多长
int length = 0;
while (value[length] != '\0')
++length;
++length; //+ 1占空终止符
// 分配内存以保存值字符串
m_value = new char[length];
// 将实际值字符串复制到刚才分配的 m_value内存中
for (int count = 0; count < length; ++count)
m_value[count] = value[count];
}
//字符类型析构函数的完全专用化char*
template<>
Storage<char*>::~Storage()
{
delete[] m_value;
}
//如果类型char*的打印函数完全专门化
// 则打印Storage<char*>将调用Storage<T*>::print(),它只打印第一个元素
template<>
void Storage<char*>::print()
{
std::cout << m_value;
}
int main()
{
// 声明一个非指针存储以显示它的工作原理
Storage<int> myint(5);
myint.print();
//声明指针存储以显示它的工作原理
int x = 7;
Storage<int*> myintptr(&x);
// 如果myintptr在x上执行指针赋值,
// 然后改变x也会改变myintptr
x = 9;
myintptr.print();
// 动态分配临时字符串
char *name = new char[40]{ "Alex" }; // 需要C ++ 14
//如果您的编译器不兼容C ++ 14,请注释掉上面的行并取消注释
// char *name = new char[40];
// strcpy(name, "Alex");
// 存储名称
Storage< char*> myname(name);
// 删除临时字符串
delete[] name;
// 打印出我们的名字
myname.print();
}
这符合我们的预期:
5
7
Alex
当您希望类以不同方式处理时,使用部分模板类专门化来创建单独的指针和非指针实现非常有用,但这种方式对最终用户完全透明。