More Effective C++35个改善编程与设计的有效方法笔记_more effective c++下线

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

	// 注意:建立了一个显示的临时对象,这个临时对象必须被构造并在最后被析构,前缀没有这样的临时对象
	UPInt oldValue = *this; // 取回值
	// 后缀应该根据它们的前缀形式来实现
	++(*this); // 增加
	return oldValue; // 返回被取回的值
}

UPInt& operator--() // --前缀
{
	i -= 1;
	return *this;
}

const UPInt operator--(int) // --后缀
{
	UPInt oldValue = *this;
	--(*this);
	return oldValue;
}

UPInt& operator+=(int a) // +=操作符,UPInt与int相运算
{
	i += a;
	return *this;
}

UPInt& operator-=(int a)
{
	i -= a;
	return *this;
}

private:
int i;
};

int test_item_6()
{
UPInt i;
++i; // 调用i.operator++();
i++; // 调用i.operator++(0);
–i; // 调用i.operator–();
i–; // 调用i.operator–(0);

//i++++; // 注意:++后缀返回的是const UPInt

return 0;

}


无论是increment或decrement的前缀还是后缀都只有一个参数,为了解决这个语言问题,C++规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0作为int参数的值给该函数。


前缀形式有时叫做”增加然后取回”,后缀形式叫做”取回然后增加”。


**当处理用户定义的类型时,尽可能地使用前缀increment****,因为它的效率较高**。


**7.** **不要重载”&&”, “||”,****或”,”**



int test_item_7()
{
// if (expression1 && expression2)
// 如果重载了操作符&&,对于编译器来说,等同于下面代码之一
// if (expression1.operator&&(expression2)) // when operator&& is a member function
// if (operator&&(expression1, expression2)) // when operator&& is a global function

return 0;

}


与C一样,C++使用布尔表达式短路求值法(short-circuit evaluation)。这表示一旦确定了布尔表达式的真假值,即使还有部分表达式没有被测试,布尔表达式也停止运算。


C++允许根据用户定义的类型,来定制&&和||操作符。方法是重载函数operator&&和operator||,你能在全局重载或每个类里重载。风险:你以函数调用法替代了短路求值法。函数调用法与短路求值法是绝对不同的。首先当函数被调用时,需要运算其所有参数。第二是C++语言规范没有定义函数参数的计算顺序,所以没有办法知道表达式1与表达式2哪一个先计算。完全可能与具有从左参数到右参数计算顺序的短路计算法相反。因此如果你重载&&或||,就没有办法提供给程序员他们所期望和使用的行为特性,所以不要重载&&和||。


同样的理由也适用于逗号操作符。逗号操作符用于组成表达式。一个包含逗号的表达式首先计算逗号左边的表达式,然后计算逗号右边的表达式;整个表达式的结果是逗号右边表达式的值。如果你写一个非成员函数operator,你不能保证左边的表达式先于右边的表达式计算,因为函数(operator)调用时两个表达式作为参数被传递出去。但是你不能控制函数参数的计算顺序。所以非成员函数的方法绝对不行。成员函数operator,你也不能依靠于逗号左边表达式先被计算的行为特性,因为编译器不一定必须按此方法去计算。因此你不能重载逗号操作符,保证它的行为特性与其被料想的一样。重载它是完全轻率的行为。


**8.** **理解各种不同含义的new****和delete**



class Widget8 {
public:
Widget8(int widget8Size) {}
};

void* mallocShared(size_t size)
{
return operator new(size);
}

void freeShared(void* memory)
{
operator delete(memory);
}

Widget8* constructWidget8InBuffer(void* buffer, int widget8Size)
{
return new(buffer) Widget8(widget8Size); // new操作符的一个用法,需要使用一个额外的变量(buffer),当new操作符隐含调用operator new函数时,把这个变量传递给它
// 被调用的operator new函数除了待有强制的参数size_t外,还必须接受void指针参数,指向构造对象占用的内存空间。这个operator new就是placement new,它看上去像这样:
// void * operator new(size_t, void
location) { return location; }
}

int test_item_8()
{
std::string* ps = new std::string(“Memory Management”); // 使用的new是new操作符(new operator)
//void * operator new(size_t size); // 函数operator new通常声明
void* rawMemory = operator new(sizeof(std::string)); // 操作符operator new将返回一个指针,指向一块足够容纳一个string类型对象的内存
operator delete(rawMemory);

delete ps; // ps->~std::string(); operator delete(ps);

void* buffer = operator new(50*sizeof(char)); // 分配足够的内存以容纳50个char,没有调用构造函数
operator delete(buffer); // 释放内存,没有调用析构函数. 这与在C中调用malloc和free等同OA

void* sharedMemory = mallocShared(sizeof(Widget8));
Widget8* pw = constructWidget8InBuffer(sharedMemory, 10); // placement new
//delete pw; // 结果不确定,共享内存来自mallocShared,而不是operator new
pw->~Widget8(); // 正确,析构pw指向的Widget8,但是没有释放包含Widget8的内存
freeShared(pw); // 正确,释放pw指向的共享内存,但是没有调用析构函数

return 0;

}


**new****操作符(new operator)****和new****操作(operator new)****的区别:**


new操作符就像sizeof一样是语言内置的,你不能改变它的含义,它的功能总是一样的。它要完成的功能分成两部分。第一部分是分配足够的内存以便容纳所需类型的对象。第二部分是它调用构造函数初始化内存中的对象。new操作符总是做这两件事情,你不能以任何方式改变它的行为。你所能改变的是如何为对象分配内存。new操作符调用一个函数来完成必须的内存分配,你能够重写或重载这个函数来改变它的行为。new操作符为分配内存所调用函数的名字是operator new。


函数operator new通常声明:返回值类型是void\*,因为这个函数返回一个未经处理(raw)的指针,未初始化的内存。参数size\_t确定分配多少内存。你能增加额外的参数重载函数operator new,但是第一个参数类型必须是size\_t。就像malloc一样,operator new的职责只是分配内存。它对构造函数一无所知。把operator new返回的未经处理的指针传递给一个对象是new操作符的工作。


placement new:特殊的operator new,接受的参数除了size\_t外还有其它。


new操作符(new operator)与operator new关系:你想在堆上建立一个对象,应该用new操作符。它既分配内存又为对象调用构造函数。如果你仅仅想分配内存,就应该调用operator new函数,它不会调用构造函数。如果你想定制自己的在堆对象被建立时的内存分配过程,你应该写你自己的operator new函数,然后使用new操作符,new操作符会调用你定制的operator new。如果你想在一块已经获得指针的内存里建立一个对象,应该用placement new。


Deletion and Memory Deallocation:为了避免内存泄漏,每个动态内存分配必须与一个等同相反的deallocation对应。函数operator delete与delete操作符的关系与operator new与new操作符的关系一样。


如果你用placement new在内存中建立对象,你应该避免在该内存中用delete操作符。因为delete操作符调用operator delete来释放内存,但是包含对象的内存最初不是被operator nen分配的,placement new只是返回转到给它的指针。


Arrays:operator new[]、operator delete[]


**9.** **使用析构函数防止资源泄漏**


用一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源,这种思想不只是可以运用在指针上,还能用在其它资源的分配和释放上。


资源应该被封装在一个对象里,遵循这个规则,你通常就能够避免在存在异常环境里发生资源泄漏,通过智能指针的方式。


C++确保删除空指针是安全的,所以析构函数在删除指针前不需要检测这些指针是否指向了某些对象。


**10.** **在构造函数中防止资源泄漏**


C++仅仅能删除被完全构造的对象(fully constructed objects),只有一个对象的构造函数完全运行完毕,这个对象才被完全地构造。C++拒绝为没有完成构造操作的对象调用析构函数。


在构造函数中可以使用try catch throw捕获所有的异常。更好的解决方法是通过智能指针的方式。


如果你用对应的std::unique\_ptr对象替代指针成员变量,就可以防止构造函数在存在异常时发生资源泄漏,你也不用手工在析构函数中释放资源,并且你还能像以前使用非const指针一样使用const指针,给其赋值。


std::unique\_ptr的使用参考:<https://blog.youkuaiyun.com/fengbingchun/article/details/52203664>


**11.** **禁止异常信息(exceptions)****传递到析构函数外**


禁止异常传递到析构函数外有两个原因:第一能够在异常传递的堆栈辗转开解(stack-unwinding)的过程中,防止terminate被调用。第二它能帮助确保析构函数总能完成我们希望它做的所有事情。


**12.** **理解”****抛出一个异常”****与”****传递一个参数”****或”****调用一个虚函数”****间的差异**


你调用函数时,程序的控制权最终还会返回到函数的调用处,但是当你抛出一个异常时,控制权永远不会回到抛出异常的地方。


C++规范要求被作为异常抛出的对象必须被复制。即使被抛出的对象不会被释放,也会进行拷贝操作。抛出异常运行速度比参数传递要慢。


当异常对象被拷贝时,拷贝操作是由对象的拷贝构造函数完成的。该拷贝构造函数是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型(dynamic type)对应类的拷贝构造函数。


catch子句中进行异常匹配时可以进行两种类型转换:第一种是继承类与基类间的转换。一个用来捕获基类的catch子句也可以处理派生类类型的异常。这种派生类与基类(inheritance\_based)间的异常类型转换可以作用于数值、引用以及指针上。第二种是允许从一个类型化指针(typed pointer)转变成无类型指针(untyped pointer),所以带有const void\*指针的catch子句能捕获任何类型的指针类型异常。


**catch****子句匹配顺序总是取决于它们在程序中出现的顺序**。因此一个派生类异常可能被处理其基类异常的catch子句捕获,即使同时存在有能直接处理该派生类异常的catch子句,与相同的try块相对应。不要把处理基类异常的catch子句放在处理派生类异常的catch子句的前面。


把一个对象传递给函数或一个对象调用虚拟函数与把一个对象作为异常抛出,这之间有三个主要区别:第一,异常对象在传递时总被进行拷贝;当通过传值方式捕获时,异常对象被拷贝了两次。对象作为参数传递给函数时不一定需要被拷贝。第二,对象作为异常被抛出与作为参数传递给函数相比,前者类型转换比后者要少(前者只有两种转换形式)。最后一点,catch子句进行异常类型匹配的顺序是它们在源代码中出现的顺序,第一个类型匹配成功的catch将被用来执行。当一个对象调用一个虚拟函数时,被选择的函数位于与对象类型匹配最佳的类里,即使该类不是在源代码的最前头。


try catch介绍参考:<https://blog.youkuaiyun.com/fengbingchun/article/details/65939258>


**13.** **通过引用(reference)****捕获异常**


通过指针捕获异常不符合C++语言本身的规范。四个标准的异常----bad\_alloc(当operator new不能分配足够的内存时被抛出);bad\_cast(当dynamic\_cast针对一个引用(reference)操作失败时被抛出);bad\_typeid(当dynamic\_cast对空指针进行操作时被抛出);bad\_exception(用于unexpected异常)----都不是指向对象的指针,所以你必须通过值或引用来捕获它们。


std::exception的介绍参考:<https://blog.youkuaiyun.com/fengbingchun/article/details/78303734>


**14.** **审慎使用异常规格(exception specifications)**


如果一个函数抛出一个不在异常规格范围里的异常,系统在运行时能够检测出这个错误,然后一个特殊函数std::unexpected将被自动地调用(This function is automatically called when a function throws an exception that is not listed in its dynamic-exception-specifier.)。std::unexpected缺省的行为是调用函数std::terminate,而std::terminate缺省的行为是调用函数abort。应避免调用std::unexpected。


避免在带有类型参数的模板内使用异常规格。


C++允许你用其它不同的异常类型替换std::unexpected异常,通过std::set\_unexpected。


**15.** **了解异常处理的系统开销**


采用不支持异常的方法编译的程序一般比支持异常的程序运行速度更快所占空间也更小。


为了减少开销,你应该避免使用无用的try块。如果使用try块,代码的尺寸将增加并且运行速度也会减慢。


**16.** **牢记80-20****准则(80-20 rule)**


80-20准则说的是大约20%的代码使用了80%的程序资源;大约20%的代码耗用了大约80%的运行时间;大约20%的代码使用了80%的内存;大约20%的代码执行80%的磁盘访问;80%的维护投入于大约20%的代码上。基本的观点:软件整体的性能取决于代码组成中的一小部分。


**17.** **考虑使用lazy evaluation(****懒惰计算法)**


在某些情况下要求软件进行原来可以避免的计算,这时lazy evaluation才是有用的。


**18.** **分期摊还期望的计算**


over-eager evaluation(过度热情计算法):在要求你做某些事情以前就完成它们。隐藏在over-eager evaluation后面的思想是如果你认为一个计算需要频繁进行,你就可以设计一个数据结构高效地处理这些计算需求,这样可以降低每次计算需求时的开销。


当你必须支持某些操作而不总需要其结果时,lazy evaluation是在这种时候使用的用以提高程序效率的技术。当你必须支持某些操作而其结果几乎总是被需要或不止一次地需要时,over-eager是在这种时候使用的用以提高程序效率的一种技术。


**19.** **理解临时对象的来源**



size_t countChar(const std::string& str, char ch)
{
// 建立一个string类型的临时对象,通过以buffer做为参数调用string的构造函数来初始化这个临时对象,
// countChar的参数str被绑定在这个临时的string对象上,当countChar返回时,临时对象自动释放

// 将countChar(const std::string& str, char ch)修改为countChar(std::string& str, char ch)则会error
return 1;

}

#define MAX_STRING_LEN 64

int test_item_19()
{
char buffer[MAX_STRING_LEN];
char c;

std::cin >> c >> std::setw(MAX_STRING_LEN) >> buffer;
std::cout<<"There are "<<countChar(buffer, c)<<" occurrences of the character "<<c<<" in "<<buffer<<std::endl;

return 0;

}


在C++中真正的临时对象是看不见的,它们不出现在你的源代码中。建立一个没有命名的非堆(non-heap)对象会产生临时对象。这种**未命名的对象通常在两种条件下产生:为了使函数成功调用而进行隐式类型转换和函数返回对象时**。


仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些类型转换。当传递一个非常量引用(reference-to-non-const)参数对象,就不会发生。


C++语言禁止为非常量引用(reference-to-non-const)产生临时对象。


临时对象是有开销的,所以你应该尽可能地去除它们。在任何时候只要见到常量引用(reference-to-const)参数,就存在建立临时对象而绑定在参数上的可能性。在任何时候只要见到函数返回对象,就会有一个临时对象被建立(以后被释放)。


**20.** **协助完成返回值优化**



class Rational20 {
public:
Rational20(int numerator = 0, int denominator = 1) {}

int numerator() const { return 1; }
int denominator() const { return 2; }

};

const Rational20 operator*(const Rational20& lhs, const Rational20& rhs)
{
// 以某种方法返回对象,能让编译器消除临时对象的开销:这种技巧是返回constructor argument而不是直接返回对象
return Rational20(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}

int test_item_20()
{
Rational20 a = 10;
Rational20 b(1, 2);
Rational20 c = a * b;

return 0;

}


一些函数(operator\*也在其中)必须要返回对象。这就是它们的运行方法。


C++规则允许编译器优化不出现的临时对象(temporary objects out of existence)。


**21.** **通过重载避免隐式类型转换**



class UPInt21 { // unlimited precision integers class
public:
UPInt21() {}
UPInt21(int value) {}
};

const UPInt21 operator+(const UPInt21& lhs, const UPInt21& rhs) // add UPInt21+UPInt21
{
return UPInt21(1);
}

const UPInt21 operator+(const UPInt21& lhs, int rhs) // add UPInt21+int
{
return UPInt21(1);
}

const UPInt21 operator+(int lhs, const UPInt21& rhs) // add int+UPInt21
{
return UPInt21(1);
}

int test_item_21()
{
UPInt21 upi1, upi2;
UPInt21 upi3 = upi1 + upi2; // 正确,没有由upi1或upi2生成临时对象
upi3 = upi1 + 10; // 正确,没有由upi1或10生成临时对象
upi3 = 10 + upi2; // 正确,没有由10或upi2生成临时对象

// 注意:注释掉上面的operator+(UPInt21&, int)和operator+(int, UPInt21&)也正确,但是会通过临时对象把10转换为UPInt21

return 0;

}


在C++中有一条规则是每一个重载的operator必须带有一个用户定义类型(user-defined type)的参数。


利用重载避免临时对象的方法不只是用在operator函数上。


没有必要实现大量的重载函数,除非你有理由确信程序使用重载函数以后其整体效率会有显著的提高。


**22.** **考虑用运算符的赋值形式(op=)****取代其单独形式(op)**



class Rational22 {
public:
Rational22(int numerator = 0, int denominator = 1) {}
Rational22& operator+=(const Rational22& rhs) { return *this; }
Rational22& operator-=(const Rational22& rhs) { return *this; }
};

// operator+根据operator+=实现
const Rational22 operator+(const Rational22& lhs, const Rational22& rhs)
{
return Rational22(lhs) += rhs;
}

// operator-根据operator-=实现
const Rational22 operator-(const Rational22& lhs, const Rational22& rhs)
{
return Rational22(lhs) -= rhs;
}


就C++来说,operator+、operator=和operator+=之间没有任何关系,因此如果你想让三个operator同时存在并具有你所期望的关系,就必须自己实现它们。同理,operator-, \*, /, 等等也一样。


确保operator的赋值形式(assignment version)(例如operator+=)与一个operator的单独形式(stand-alone)(例如operator+)之间存在正常的关系,一种好方法是后者(指operator+)根据前者(指operator+=)来实现。


**23.** **考虑变更程序库**


不同的程序库在效率、可扩展性、移植性、类型安全和其它一些领域上蕴含着不同的设计理念,通过变换使用给予性能更多考虑的程序库,你有时可以大幅度地提供软件的效率。


**24.** **理解虚拟函数、多继承、虚基类和RTTI****所需的代码**


当调用一个虚拟函数时,被执行的代码必须与调用函数的对象的动态类型相一致;指向对象的指针或引用的类型是不重要的。大多数编译器是使用virtual table和virtual table pointers,通常被分别地称为vtbl和vptr。


一个vtbl通常是一个函数指针数组。(一些编译器使用链表来代替数组,但是基本方法是一样的)在程序中的每个类只要声明了虚函数或继承了虚函数,它就有自己的vtbl,并且类中vtbl的项目是指向虚函数实现体的指针。


你必须为每个包含虚函数的类的virtual table留出空间。类的vtbl的大小与类中声明的虚函数的数量成正比(包括从基类继承的虚函数)。**每个类应该只有一个****virtual table**,所以virtual table所需的空间不会太大,但是如果你有大量的类或者在每个类中有大量的虚函数,你会发现vtbl会占用大量的地址空间。


一些原因导致现在的编译器一般总是忽略虚函数的inline指令。


Virtual table只实现了虚拟函数的一半机制,如果只有这些是没有用的。只有用某种方法指出每个对象对应的vtbl时,它们才能使用。这是virtual table pointer的工作,它来建立这种联系。每个声明了虚函数的对象都带着它,它是一个看不见的数据成员,指向对应类的virtual table。这个看不见的数据成员也称为vptr,被编译器加在对象里,位置只有编译器知道。


关于虚函数表的介绍参考:<https://blog.youkuaiyun.com/fengbingchun/article/details/79592347>


**虚函数是不能内联的**。这是因为”内联”是指”在编译期间用被调用的函数体本身来代替函数调用的指令”,但是虚函数的”虚”是指”直到运行时才能知道要调用的是哪一个函数”。


RTTI(运行时类型识别)能让我们在运行时找到对象和类的有关信息,所以肯定有某个地方存储了这些信息让我们查询。这些信息被存储在类型为type\_info的对象里,你能通过使用typeid操作符访问一个类的type\_info对象。


关于typeid的使用参考:<https://blog.youkuaiyun.com/fengbingchun/article/details/51866559>


RTTI被设计为在类的vtbl基础上实现。


**25.** **将构造函数和非成员函数虚拟化**


虚拟构造函数是指能够根据输入给它的数据的不同而建立不同类型的对象。虚拟拷贝构造函数能返回一个指针,指向调用该函数的对象的新拷贝。类的虚拟拷贝构造函数只是调用它们真正的拷贝构造函数。**被派生类重定义的虚拟函数不用必须与基类的虚拟函数具有一样的返回类型**。如果函数的返回类型是一个指向基类的指针(或一个引用),那么派生类的函数可以返回一个指向基类的派生类的指针(或引用)。


**26.** **限制某个类所能产生的对象数量**


阻止建立某个类的对象,最容易的方法就是把该类的构造函数声明在类的private域。


**27.** **要求或禁止在堆中产生对象**



// 判断一个对象是否在堆中, HeapTracked不能用于内建类型,因为内建类型没有this指针
typedef const void* RawAddress;
class HeapTracked { // 混合类,跟踪
public:
class MissingAddress {}; // 从operator new返回的ptr异常类
virtual ~HeapTracked() = 0;
static void* operator new(size_t size);
static void operator delete(void* ptr);
bool isOnHeap() const;

private:
static std::list addresses;
};

std::list HeapTracked::addresses;

HeapTracked::~HeapTracked() {}

void* HeapTracked::operator new(size_t size)
{
void* memPtr = ::operator new(size);
addresses.push_front(memPtr);
return memPtr;
}

void HeapTracked::operator delete(void* ptr)
{
std::list::iterator it = std::find(addresses.begin(), addresses.end(), ptr);
if (it != addresses.end()) {
addresses.erase(it);
::operator delete(ptr);
} else {
throw MissingAddress(); // ptr就不是用operator new分配的,所以抛出一个异常
}
}

bool HeapTracked::isOnHeap() const
{
// 生成的指针将指向"原指针指向对象内存"的开始处
// 如果HeapTracked::operator new为当前对象分配内存,这个指针就是HeapTracked::operator new返回的指针
const void* rawAddress = dynamic_cast<const void*>(this);
std::list::iterator it = std::find(addresses.begin(), addresses.end(), rawAddress);
return it != addresses.end();
}

class Asset : public HeapTracked {};

// 禁止堆对象
class UPNumber27 {
private:
static void* operator new(size_t size);
static void operator delete(void* ptr);
};

void* UPNumber27::operator new(size_t size)
{
return ::operator new(size);
}

void UPNumber27::operator delete(void* ptr)
{
::operator delete(ptr);
}

class Asset27 {
public:
Asset27(int initValue) {}

private:
UPNumber27 value;
};

int test_item_27()
{
UPNumber27 n1; // okay
static UPNumber27 n2; // also okay
//UPNumber27* p = new UPNumber27; // error, attempt to call private operator new

// UPNumber27的operator new是private这一点 不会对包含UPNumber27成员对象的对象的分配产生任何影响
Asset27* pa = new Asset27(100); // 正确,调用Asset::operator new或::operator new,不是UPNumber27::operator new

return 0;

}


禁止堆对象:禁止用于调用new,利用new操作符总是调用operator new函数这点来达到目的,可以自己声明这个函数,而且你可以把它声明为private。


**28.** **灵巧(smart)****指针**



// 大多数灵巧指针模板
template
class SmartPtr {
public:
SmartPtr(T* realPtr = 0); // 建立一个灵巧指针指向dumb pointer(内建指针)所指的对象,未初始化的指针,缺省值为0(null)
SmartPtr(const SmartPtr& rhs); // 拷贝一个灵巧指针
~SmartPtr(); // 释放灵巧指针
// make an assignment to a smart ptr
SmartPtr& operator=(const SmartPtr& rhs);
T* operator->() const; // dereference一个灵巧指针以访问所指对象的成员
T& operator*() const; // dereference灵巧指针

private:
T* pointee; // 灵巧指针所指的对象
};


灵巧指针是一种外观和行为都被设计成与内建指针相类似的对象,不过它能提供更多的功能。它们有许多应用的领域,包括资源管理和重复代码任务的自动化。


在C++11中auto\_ptr已经被废弃,用unique\_ptr替代。


std::unique\_ptr的使用参考:<https://blog.youkuaiyun.com/fengbingchun/article/details/52203664>


**29.** **引用计数**



class String {
public:
String(const char* initValue = “”);
String(const String& rhs);
String& operator=(const String& rhs);
const char& operator[](int index) const; // for const String
char& operator[](int index); // for non-const String
~String();

private:
// StringValue的主要目的是提供一个空间将一个特别的值和共享此值的对象的数目联系起来
struct StringValue { // holds a reference count and a string value
int refCount;
char* data;
bool shareable; // 标志,以指出它是否为可共享的
StringValue(const char* initValue);
~StringValue();
};

StringValue* value; // value of this String

};

String::String(const char* initValue) : value(new StringValue(initValue))
{}

String::String(const String& rhs)
{
if (rhs.value->shareable) {
value = rhs.value;
++value->refCount;
} else {
value = new StringValue(rhs.value->data);
}
}

String& String::operator=(const String& rhs)
{
if (value == rhs.value) { // do nothing if the values are already the same
return *this;
}

if (value->shareable && --value->refCount == 0) { // destroy *this's value if no one else is using it
	delete value;
}

if (rhs.value->shareable) {
	value = rhs.value; // have *this share rhs's value
	++value->refCount;
} else {
	value = new StringValue(rhs.value->data);
}

return *this;

}

const char& String::operator[](int index) const
{
return value->data[index];
}

char& String::operator[](int index)
{
// if we’re sharing a value with other String objects, break off a separate copy of the value fro ourselves
if (value->refCount > 1) {
–value->refCount; // decrement current value’s refCount, becuase we won’t be using that value any more
value = new StringValue(value->data); // make a copy of the value for ourselves
}

value->shareable = false;
// return a reference to a character inside our unshared StringValue object
return value->data[index];

}

String::~String()
{
if (–value->refCount == 0) {
delete value;
}
}

String::StringValue::StringValue(const char* initValue) : refCount(1), shareable(true)
{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}

String::StringValue::~StringValue()
{
delete[] data;
}

// 基类,任何需要引用计数的类都必须从它继承
class RCObject {
public:
void addReference() { ++refCount; }
void removeReference() { if (–refCount == 0) delete this; } // 必须确保RCObject只能被构建在堆中
void markUnshareable() { shareable = false; }
bool isShareable() const { return shareable; }
bool isShared() const { return refCount > 1; }

protected:
RCObject() : refCount(0), shareable(true) {}
RCObject(const RCObject& rhs) : refCount(0), shareable(true) {}
RCObject& operator=(const RCObject& rhs) { return *this; }
virtual ~RCObject() = 0;

private:
int refCount;
bool shareable;

};

RCObject::~RCObject() {} // virtual dtors must always be implemented, even if they are pure virtual and do nothing

// template class for smart pointers-to-T objects. T must support the RCObject interface, typically by inheriting from RCObject
template
class RCPtr {
public:
RCPtr(T* realPtr = 0) : pointee(realPtr) { init(); }
RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); }
~RCPtr() { if (pointee) pointee->removeReference(); }

RCPtr& operator=(const RCPtr& rhs)
{
	if (pointee != rhs.pointee) { // skip assignments where the value doesn't change
		if (pointee)
			pointee->removeReference(); // remove reference to current value

		pointee = rhs.pointee; // point to new value
		init(); // if possible, share it else make own copy
	}

	return *this;
}

T* operator->() const { return pointee; }
T& operator*() const { return *pointee; }

private:
T* pointee; // dumb pointer this object is emulating

void init() // common initialization
{
	if (pointee == 0) // if the dumb pointer is null, so is the smart one
		return;

	if (pointee->isShareable() == false) // if the value isn't shareable copy it
		pointee = new T(*pointee);

	pointee->addReference(); // note that there is now a new reference to the value
}

};

// 将StringValue修改为是从RCObject继承
// 将引用计数功能移入一个新类(RCObject),增加了灵巧指针(RCPtr)来自动处理引用计数
class String2 {
public:
String2(const char* value = “”) : value(new StringValue(value)) {}
const char& operator[](int index) const { return value->data[index]; } // for const String2

char& operator[](int index) // for non-const String2
{
	if (value->isShared())
		value = new StringValue(value->data);
	value->markUnshareable();
	return value->data[index];
}

private:
// StringValue的主要目的是提供一个空间将一个特别的值和共享此值的对象的数目联系起来
struct StringValue : public RCObject { // holds a reference count and a string value
char* data;

	StringValue(const char* initValue) { init(initValue); }
	StringValue(const StringValue& rhs) { init(rhs.data); }

	void init(const char* initValue)
	{
		data = new char[strlen(initValue) + 1];
		strcpy(data, initValue);
	}

	~StringValue() { delete [] data; }
};

RCPtr<StringValue> value; // value of this String2

};

int test_item_29()
{

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

e的主要目的是提供一个空间将一个特别的值和共享此值的对象的数目联系起来
struct StringValue : public RCObject { // holds a reference count and a string value
char* data;

	StringValue(const char* initValue) { init(initValue); }
	StringValue(const StringValue& rhs) { init(rhs.data); }

	void init(const char* initValue)
	{
		data = new char[strlen(initValue) + 1];
		strcpy(data, initValue);
	}

	~StringValue() { delete [] data; }
};

RCPtr<StringValue> value; // value of this String2

};

int test_item_29()
{

[外链图片转存中…(img-41g4GCtJ-1715838191633)]
[外链图片转存中…(img-iDcRxG5Q-1715838191633)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值