《More Effective C++》读书笔记-技术

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;
}
作者 : Scott Meyers 译序、导读 : 侯捷 译序(侯捷) C++ 是一个难学易用的语言! C++ 的难学,不仅在其广博的语法,以及语法背後的语意,以及语意背後的深层思维,以及深层思维背後的物件模型;C++ 的难学,还在於它提供了四种不同(但相辅相成)的程式设计思维模式:procedural-based,object-based,object-oriented,generic paradigm。 世上没有白吃的午餐。又要有效率,又要有弹性,又要前瞻望远,又要回溯相容,又要能治大国,又要能烹小鲜,学习起来当然就不可能太简单。 在如此庞大复杂的机制下,万千使用者前仆後续的动力是:一旦学成,妙用无穷。C++ 相关书籍之多,车载斗量;如天上繁星,如过江之鲫。广博如四库全书者有之(The C++ Programming Language、C++ Primer),深奥如重山复水者有之(The Annotated C++ Reference Manual, Inside the C++ Object Model),细说历史者有之(The Design and Evolution of C++, Ruminations on C++),独沽一味者有之(Polymorphism in C++, Genericity in C++),独树一帜者有之(Design Patterns,Large Scale C++ Software Design, C++ FAQs),程式库大全有之(The C++ Standard Library),另辟蹊径者有之(Generic Programming and the STL),工程经验之累积亦有之(Effective C++, More Effective C++, Exceptional C++)。 这其中,「工程经验之累积」对已具C++ 相当基础的程式员而言,有著致命的吸引力与立竿见影的帮助。Scott Meyers 的Effective C++ 和More Effective C++ 是此类佼佼,Herb Sutter 的Exceptional C++ 则是後起之秀。 这类书籍的一个共通特色是轻薄短小,并且高密度地纳入作者浸淫於C++/OOP 领域多年而广泛的经验。它们不但开展读者的视野,也为读者提供各种C++/OOP 常见问题或易犯错误的解决模型。某些小范围主题诸如「在base classes 中使用virtual destructor」、「令operator= 传回*this 的reference」,可能在百科型C++ 语言书籍中亦曾概略提过,但此类书籍以深度探索的方式,让我们了解问题背後的成因、最佳的解法、以及其他可能的牵扯。至於大范围主题,例如smart pointers, reference counting, proxy classes,double dispatching, 基本上已属design patterns 的层级! 这些都是经验的累积和心血的结晶。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值