C++类模板的分离编译
过去很多类模板都是整个类连同实现都放在一个头文件里,像STL库就是遵循这样的策略来实现类模板的。现在的标准正试图矫正这种局面。
在实现中又许多函数模板。这意味着每个函数都必须包含模板声明,并且在使用作用域操作符的时候,类的名称必须通过模板变量来实例化。
比如一个operator=的代码:
template <typename Object>
const MemoryCell <Object> &
MemoryCell<Object>::operator=(const MemoryCell<Object> & rhs)
{
if(this != &rhs)
storeValue = rhs.storedValue;
return *this;
}
头文件内容
将声明和实现都放在头文件中,对于类是行不通的,因为如果几个不同的源文件都有处理这个头文件的包含指令的话,就会出现重复定义函数的情况。但是,如果在头文件里的知识模板而不是真实的类,就不会有问题。
函数对象
在一个函数的编写中,需要像接受参数一样接受比较函数,用该比较函数来决定两个对象的大小。
使用这种方法的原因是,将比较的规则从对象中剥离出来,用一个比较函数来决定。
一个如传递参数一样传递函数的巧妙办法是:定义一个包含零个数据和一个成员函数的类,然后传递这个类的实例。从效果上看就是,通过将其放在对象中实现了函数的传递,该对象通常称为函数对象。
举例:
findMax函数获得第二个形参,该形参为泛型类型。为使findMax模板可以无误地扩展,泛型类型必须含有名为isLessThan的成员函数。该成员函数获得第一个泛型类型的两个形参,并返回一个bool值。
template <typename T, typename Comparator>
const T & findMax(const vector<T> & arr, Comparator cmp)
{
int maxIndex = 0;
for(int i=1;i<arr.size();i++)
if(cmp.isLessThan(arr[maxIndex],arr[i]))
maxIndex = i;
return arr[maxIndex];
}
class CaseInsensitiveCompare
{
public:
bool isLessThan(const string & lhs, const string & rhs) const
{
return stricmp(lhs.c_str(), rhs.c_str()) < 0;
}
};
int main()
{
vector<string> arr(3);
arr[0] = "ZERbad";
arr[1] = "alligator";
arr[2] = "crocodile";
cout << findMax(arr, CaseInsensitiveCompare()) << endl;
return 0;
}
智能指针
包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针的地址,而不会复制指针指向的对象。
当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。
智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
管理指针成员
设计具有指针成员的类时,类的设计者必须首先需要决定的是该指针应提供什么行为。将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一个对象时,另一指针的用户还认为基础对象仍然存在。
大多数C++类采用以下三种方法之一管理指针成员:
- 指针成员采用常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制。
- 类可以实现所谓的“智能指针”行为。指针所指向的对象时共享的,但类能够防止悬垂指针。
- 类采用值型行为。指针所指向的对象时唯一的,由每个类对象独立管理。
悬垂指针
具有指针成员且使用默认合成复制构造函数的类,因为类的复制时,直接复制指针。用户必须保证只要类对象存在,该指针指向的对象就存在。如果之前指向的对象不再存在了,此类指针称为悬垂指针。结果未定义,往往导致程序错误,难以检测。我们可以引入智能指针来防止悬垂指针的出现。
关于智能指针的具体内容请移步智能指针类和OpenCV的Ptr模板类
non-const成员函数调用const成员函数
在operator[]函数的编写中,我们要做的其实是实现其机能一次,并使用它两次。
你必须令其中一个调用另一个。这促使我们将常量性转除。
当const operator[]完全做了non-const版本该做的一切时,如果将返回值的const转除是安全的,因为不论谁调用non-const operator[]都一定有个non-const对象,否则就不能调用non-const函数。所以令non-const operator调用其const函数式一个避免代码重复的安全做法。
class A{
public:
const char& operator[](std::size_t position) const
{
...
...
return text[position];
}
char& operator[](std::size_t position)
{
return
const_cast<char &>(
static_cast<const A&>(*this)[position]
);
}
private:
std::string text;
}
要让non-const operator[]调用const operator[],但non-const operator[]内部如果只是单纯调用operator[],会递归调用自己。所以我们必须明确指出调用的是const operator[]。这里将*this从原始类型A&转型为const A&。这样为 *this 添加const。这里我们使用static_cast进行安全转型。
第二次则是从const operator[]的返回值中移除const。这里只能借助const_cast完成。
如果在const函数内调用non-const函数,就会改变对象的逻辑状态,所以const成员函数调用non-const成员函数是一种错误行为;而non-const成员函数本来就可以对其对象做任何动作,所以在其中调用一个const成员函数并不会带来风险。
转载请注明作者Jason Ding及其出处
Github博客主页(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
百度搜索jasonding1354进入我的博客主页