25、将构造函数和非成员函数虚化
1、这里所谓的虚拟构造函数,并不是真的指在构造函数前面加上 virtual 修饰符,而是指能够根据传入不同的参数建立不同继承关系类型的对象。
class NLComponent { // 抽象基类,其中内含至少一个纯虚函数
public:
...
};
class TextBlock: public NLComponent{ // 没有内含任何纯虚函数
public:
...
};
class Graphic: public NLComponent{ // 没有内含任何纯虚函数
public:
...
};
class NewsLetter { // 一份实时通信是由一系列的NLComponent对象构成的
public:
NewsLetter(istream& str);//NewsLetter拥有一个istream为自变量
//的构造函数,函数由stream读取数据以便产生必要的核心数据结构
...
private:
//从str读取下一个NLComponent的数据,产生组件,并返回一个指针指向它!
static NLComponent* readComponent(istream &str);
list<NLComponent *> components;
};
NewsLetter::NewsLetter(istream &str)
{
while(str)
{//将readComponent返回的指针加到Component list尾端
components.push_back(readComponent(str));
}
}
由以上代码可知:
(1)NLComponent的Constructor并没有虚拟化,他只是通过readComponent读取istream产生不同类型的组件,返回组件的指针,之后将指针以基类指针(NLComponent*)类型存储起来,用来在以后的调用可以实现多态,这样就是Virtual Constructor。
(2)NewsLetter类的readComponent函数根据输入的字符串不同产生不同的对象。它产生新对象,所以行为好像constructor,但它能够产生不同类型的对象,所以称为一个virtual constructor。所谓virtual constructor是指能够根据输入给它的数据的不同而产生不同类型的对象。!!!!
2、virtual copy constructor:所谓virtual copy constructor,它会返回一个指针,指向其调用者(某对象)的一个新副本。
class NLComponent{
public:
// 声明virtual copy constructor
virtual NLComponent *clone() const = 0;
};
class TextBlock:public NLComponent{
public:
virtual TextBlock* clone() const
{ return new TextBlock(*this);}
};
class Graphic:public NLComponent {
public:
virtual Graphic* clone() const
{return new Graphic(*this);}
};
由代码可知,类的virtual copy constructor只是调用真正的copy constructor而已。并且执行的动作和效果完全一致。
当子类重新定义其基类的一个虚函数时,不需要一定得声明与原本相同的返回类型。如果函数的返回类型是一个指向基类的指针(或引用),那么子类的函数可以返回一个指针(或引用),指向该基类的一个子类。
这也就是为什么即使NLComponent的clone函数返回类型是NLComponent*
,TextBlock的clone函数却可以返回TextBlock*
,而Graphic的clone函数可以返回Graphic*的原因。
3、将non-member functions虚化
非成员函数虚化,这里也并不是指使用 virtual 来修饰非成员函数。而使写一个虚函数做实际工作,再写一个什么都不做的非虚函数,只负责调用虚函数。
#include<iostream>
using namespace std;
class NLComponent{
public:
virtual ostream& print(ostream& s) const = 0;
};
class TextBlock:public NLComponent{
public:
virtual ostream& print(ostream& s) const
{
s << "TextBlock";
return s;
}
};
class Graphic : public NLComponent{
public:
virtual ostream& print(ostream& s) const
{
s << "Graphic";
return s;
}
};
inline ostream& operator<<(ostream& s, const NLComponent& c)
{
return c.print(s);
}
int main(){
TextBlock tx;
Graphic gc;
cout << tx << endl;
cout << gc << endl;
return 0;
}
上述代码可知:声明一个虚函数(print)作为打印之用,并在TextBlock和Graphic中定义它。定义一个operator<<的non-member function,展现出类似print虚函数一般的行为。
26、限制某个 class 所能产生的对象数量
限制对象个数:建立一个基类,构造函数和复制构造函数中计数加1,若超过最大值则抛出异常;析构函数中计数减1。
27、要求(或禁止)对象产生于 heap 之中
1、要求对象产生于堆中
栈上的对象肯定调用构造方法和析构方法(离开作用域的时候),因此,要求对象只能产生于heap之中,也就是禁止栈上产生对象,解决办法有两种:将所有的构造方法声明为private,或者将析构方法声明为private。
将构造方法或者析构方法声明为private,将导致两个问题:阻止了继承和内含(组合)。
class UPNumber { ... }; //将析构函数或构造函数声明为private
//继承
class NonNegativeUPNumber: public UPNumber { ... }; // 错误! 构造函数或析构函数不能通过编译
class Asset {
private:
UPNumber value;//内含
... // 错误! 构造函数或析构函数不能通过编译
};
解决方法:
对于继承,可以将父类的构造函数和析构函数放大访问权限,析构函数为protected,构造函数保持其public。
对于内含一个对象,修改为内含一个指针,指向对象。
2、判断某个对象是否位于heap内
没有合适办法能判断一个对象是否在堆中,我们为什么要判断对象是否在堆上?真实的需求是,判断执行delete是否安全。那怎么办呢?
判断一个对象是否可以安全用delete删除,只需在operator new中将其指针加入一个列表,然后根据此列表判断指针是否在其中,如果在,执行delete就是安全的,否则不安全。
3、如何禁止对象产生于heap之中
在堆上创建对象,必定调用operator new分配内存,因此将operator new声明为private就好了。为了统一访问层级,可以将operator delete一同设为private。(但是仍然不能判断其是否在堆中)。
28、Smart Pointer(智能指针)
1、智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。简单来说,智能指针就是模拟指针动作的类。智能指针从模板生成,因为要与内建指针类似,必须是强类型的;模板参数确定指向对象的类型。
在C++中,我们知道,如果使用普通指针来创建一个指向某个对象的指针,那么在使用完这个对象之后我们需要自己删除它,但是由于程序员忘记删除,或在删除前抛出异常,导致没有执行删除操作,则会产生内存泄露。
在这个时候,智能指针的出现实际上就是为了可以方便的控制对象的生命期,在智能指针中,一个对象什么时候和在什么条件下要被析构或者是删除是受智能指针本身决定的,用户并不需要管理。
2、测试智能指针是否为NULL有两种方案:一种是使用类型转换,将其转换为void*,但是这样将导致类型不安全,因为不同类型的智能指针之间将能够互相比较;另一种是重载operator!,这种方案只能使用!ptr这种方式检测。
3、智能指针的继承类到基类的类型转换:使用模板成员函数。这将使得内建指针所有可以转换的类型也可以在智能指针中进行转换,但是对于间接继承的情况,必须用dynamic_cast指定其要转换的类型是直接基类还是间接基类。
29、Reference counting(引用计数)
1、使用引用计数后,对象自己拥有自己,当没有人再使用它时,它自己自动销毁自己。因此,引用计数是个简单的垃圾回收体系。此计数有两个动机:第一为了简化堆对象周边的簿记工作。第二是为了实现一种常识,所有等值对象共享同一实值,不仅节省内存,也使程序速度加快。
引用计数的最重要功能是对象共享。当有许多对象有相同的值时,将该值存储多次是一件愚蠢的事。因此让所有等值对象共享一份实值即可满足要求,这样既节省内存空间,也让速度加快(构造、析构对象费时)。
我们需要追踪引用计数的对象有多少个对象共享它。如果有一个共享对象修改做出修改时,我们不能改变引用计数对象,因为还有其他共享对象需要它。这时引用计数器开始派上用场了。这也是引用计数不得不添加的开销。亦即我们需要存储所共享的对象,也需要保存该对象的引用次数,两者是一个耦合关系。
2、引用计数的成本:每一个拥有引用计数能力的实值都携带一个引用计数器,大部分操作都需要查验或处理这个计数器,对象的实值因而需要更多内存,我们需要执行更多代码。
3、引用计数的优点:引用计数是个优化技术,其适用前提是对象常常共享实值,在这种情况下它可节省你的空间和时间。以下是引用计数改善效率的最适当时机:
(1)相对多数的对象共享相对少量的实值的时候。”对象/实值”数量比越高,引用计数带来的利益越大。
(2)对象实值产生或销毁成本很高,或是他们使用很多内存的时候。
引用计数实现的String类:(参考http://blog.csdn.net/ruan875417/article/details/48241525)
#include<iostream>
#include<string.h>
using namespace std;
class String{
public:
String(const char* initValue = nullptr);//构造函数
String(const String& rhs);//拷贝构造函数
~String();//析构函数
String& operator=(const String& rhs);//赋值运算符
const char& operator[](int index) const;//重载[]运算符,针对const Strings
char& operator[](int index);//重载[]运算符,针对non-const Strings
String operator+(const String& rhs);//重载+运算符
String& operator+=(const String& rhs);//重载+=运算符
bool operator==(const String& rhs);//重载==运算符
int getLength();//获取长度
friend istream& operator>>(istream& is, const String& str);//重载>>运算符
friend ostream& operator<<(ostream& os, const String& str);//重载<<运算符
int getRefCount();//获取引用对象的个数
private:
struct StringValue{
int refCount;//引用计数
char* data;
StringValue(const char* initValue);//构造函数
~StringValue();//析构函数
};
StringValue* value;
};
//StringValue类的构造函数
String::StringValue::StringValue(const char* initValue):refCount(1){
if (initValue == nullptr){
data = new char[1];
data[0] = '\0';
}
else{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
}
//StringValue类的析构函数
String::StringValue::~StringValue(){
delete[] data;
data = nullptr;
}
//String类的构造函数
String::String(const char* initValue):value(new StringValue(initValue))
{}
//String类的拷贝构造函数
String::String(const String& rhs) : value(rhs.value){
++value->refCount;//引用计数加1!!!
}
//String类的析构函数
String::~String(){
if (--value->refCount == 0){//析构时引用计数减1,当变为0时,没有指针指向该内存,销毁
delete value;
}
}
//String类的赋值运算符
String& String::operator=(const String& rhs){
if (value == rhs.value) //自赋值
return *this;
//赋值时左操作数引用计数减1,当变为0时,没有指针指向该内存,销毁
if (--value->refCount == 0)
delete value;
//不必开辟新内存空间,只要让指针指向同一块内存,并把该内存块的引用计数加1
value = rhs.value;
++value->refCount;
return *this;
}
//重载[]运算符,针对const Strings
const char& String::operator[](int index) const{
if(index<strlen(value->data))
return value->data[index];
}
//重载[]运算符,针对non-const Strings
char& String::operator[](int index){
if (value->refCount>1)
{//如果本对象和其他String对象共享同一实值,
//就分割(复制)出另一个副本供本对象自己使用
--value->refCount;
value = new StringValue(value->data);
}
if (index<strlen(value->data))
return value->data[index];
}
//String类的重载+运算符
String String::operator+(const String& rhs){
return String(*this) += rhs;
}
//String类的重载+=运算符
String& String::operator+=(const String& rhs){
//左操作数引用计数减1,当变为0时,没有指针指向该内存,销毁
if (--value->refCount == 0)
delete value;
//右操作数为空
if (rhs.value->data == nullptr){
value = new StringValue(value->data);
return *this;
}
//左操作数为空
if (this->value->data == nullptr){
value = new StringValue(rhs.value->data);
return *this;
}
//都不空
char* pTemp = new char[strlen(this->value->data) + strlen(rhs.value->data) + 1];
strcpy(pTemp, this->value->data);
strcat(pTemp, rhs.value->data);
value=new StringValue(pTemp);
return *this;
}
//重载==运算符
bool String::operator==(const String& rhs){
return strcmp(this->value->data, rhs.value->data) == 0 ? true : false;
}
//获取长度
int String::getLength(){
return strlen(this->value->data);
}
//重载>>运算符
istream& operator>>(istream& is, const String& str){
is >> str.value->data;
return is;
}
//重载<<运算符
ostream& operator<<(ostream& os, const String& str){
os << str.value->data;
return os;
}
//获取引用对象的个数
int String::getRefCount(){
return value->refCount;
}
int main()
{
String str1("hello world");
String str2 = str1;//调用拷贝构造函数
String str3;//调用默认构造函数
str3 = str2;//调用拷贝赋值运算符
cout << "str1的引用计数是:" << str1.getRefCount() << endl; // 3
cout << "str2的引用计数是:" << str2.getRefCount() << endl; // 3
cout << "str3的引用计数是:" << str3.getRefCount() << endl; // 3
str1[0] = 'H';//调用针对non-const Strings的重载[]运算符
cout << str1 << endl; //"Hello world"
cout << str2 << endl;//"hello world"
cout << str3 << endl;//"hello world"
cout << "str1的引用计数是:" << str1.getRefCount() << endl;//1
cout << "str2的引用计数是:" << str2.getRefCount() << endl;//2
cout << "str3的引用计数是:" << str3.getRefCount() << endl;//2
String str4("hello");//调用构造函数
String str5 = str4;//调用拷贝构造函数
String str6 = " world";//调用构造函数
str5 = str5+str6;//调用String类的重载+运算符,调用String类的拷贝赋值运算符
cout << str4 << endl; //"hello"
cout << str5 << endl; //"hello world"
cout << str6 << endl; //" world"
cout << "str4的引用计数是:" << str4.getRefCount() << endl;//1
cout << "str5的引用计数是:" << str5.getRefCount() << endl;//1
cout << "str6的引用计数是:" << str6.getRefCount() << endl;//1
String str7 = str5;//调用拷贝构造函数
String str8;//调用默认构造函数
str8 = str7;//调用String类的拷贝赋值运算符
cout << str7 << endl; //"hello world"
cout << "str5的引用计数是:" << str5.getRefCount() << endl;//3
cout << "str7的引用计数是:" << str7.getRefCount() << endl;//3
cout << "str8的引用计数是:" << str8.getRefCount() << endl;//3
str5 += str6;//调用String类的重载+=运算符
cout << str5 << endl; //"hello world world"
cout << str6 << endl; //" world"
cout << str7 << endl; //"hello world"
cout << str8 << endl; //"hello world"
cout << "str5的引用计数是:" << str5.getRefCount() << endl; //1
cout << "str6的引用计数是:" << str6.getRefCount() << endl;//1
cout << "str7的引用计数是:" << str7.getRefCount() << endl;//2
cout << "str8的引用计数是:" << str8.getRefCount() << endl;//2
return 0;
}