当你知道你需要指向某个东西,而且绝对不会改变指向其他东西,或是当你实现一个操作符而其他语法需求无法有pointers达成,你就应该选择references。任何其他时候,请采用pointers。
static_cast基本上拥有与C旧式转型相同的威力和意义,以及相同的限制。
类的两种函数可以做到类型转换:
允许编译器执行隐式类型转换,害处将多过好处。所以不要提供转换函数,除非你确定你需要它们。
前置式比后置式效率更高,没有必要的理由,应该优先选择前置式。而且后置式操作符应以前置式操作符为实现基础。因为后置形式会产生一个临时对象
“函数调用“语义和”骤死式“语义有两个重大的区别。第一,当函数调用动作被执行,所有参数值都必须评估完成。第二,C++语言规范并没有明确定义函数调用动作中各参数的评估顺序。
如果重载&&,|| 和,无法令其行为像它们应有的行为一样。
operator new 唯一的任务就是分配内存。
取得operator new 返回的内存并将之转换为一个对象,是new operator的责任。
如果你希望将对象产生于heap,请使用new operator。它不但分配内存而且为该对象调用一个constructor。
如果你只打算分配内存,请调用operator new,那就没有任何constructor会被调用。
operator new, new operator,placement new
数组:
operator new[]
new operator (其他教程称为new expression)。
这个操作符是由语言内建的,就像是sizeof那样,不能被改变意义,总是做相同的事情。它的动作分为两方面:
它总是做这两件事,无论如何不能改变其行为。
operator new:
唯一的任务就是分配内存。
比如:
void *rawMemory = operator new(sizeof(stirng));
这里operator分配了一块足够容纳一个string对象的内存。
new operator (其他教程称为new expression):
这个操作符是由语言内建的,就像是sizeof那样,不能被改变意义,总是做相同的事情。比如如下代码:
string *ps = new string("Memory Management");
它实际上会做如下这些动作:
它总是做这两件事,无论如何不能改变其行为。但是你能够改变用来容纳对象的那块内存的分配行为——通过重载operator new做到。
placement new:
placement即是一个特殊版本的operator new。它被用来在一个既有的内存块上构造对象。
有如下代码:
class Widget {
......
};
Widget *constructWidgetInBuffer(void *buffer, int widgetSize) {
return new(buffer) Widget(widgetSize);
}
此函数返回一个指针,指向一个Widget object,它被构造于传递给此函数的一块内存缓冲区上。它往往被用于将对象放置在一些固定的内存地址上。
上面那个new调用的operator new是这样的:
void* operator new(size_t, void *location) {
return location;
}
这就是placement new,它是C++ 标准库的一部分。要使用placement new,你必须用#include 。
总结:
删除与内存释放:
为了避免资源泄露,每一个动态分配行为,都必须匹配一个相应但相反的释放动作。
数组:
有如下代码:
string* ps = new string[10];
这里,内存分配不在以operator new分配,而是尤其”数组版“兄弟,一个名为operator new[] 的函数负责分配。和operator new一样,operator new[]也可以被重载。
“数组版”与”单一对象版“的new operator的第二个不同是,它所调用的constructor数量,它会针对数组中的每个对象调用一个constructor。
把资源封装在对象内,通常便可以在exceptions出现时避免泄露资源。
C++只会析构已经构造完成的对象。对象只有在其constructor执行完毕才算是完全构造妥当。
new一个对象时,如果在构造函数中抛出异常,new出来的内存会被自动释放。
C++中的new运算符和构造函数之间存在一种异常安全机制,称为构造函数异常安全。根据这个机制,在构造函数抛出异常时,会自动调用已构造子对象的析构函数来销毁子对象,其顺序是构造的逆序,并释放由new分配的内存。
处理”“构造过程中可能发生的exceptions”,相当棘手,需要在发生异常后防止资源泄露。这里可以使用智能指针来消除大部分劳役。
原因有二:
当对象被exception处理机制销毁时,如果此时抛出一个异常,因为此刻正有另一个exception处于作用状态,C++会调用terminate函数。
有如下一个函数:
istream operator>>(istream& s, Widget& w);
void passAndThrowWidget() {
Widget localWidget;
cin >> localWidget;
throw localWidget;
}
被当作函数参数的localWidget,是被以引用方式传播的。
而被当作exception的localWidget, 不论被捕获的exception是以by value或 by reference 方式传递,都会发生 localWidget 的复制行为,交到catch字句手上的正是那个副本,因为一旦控制权离开passAndThrowWidget,localWidget便离开了作用域,于是它便被销毁了。因此catch只能使用它的副本。即使是static的localWidget,也是一样。
当对象被当作exception时,复制行为是由对象的copy constructor执行的。这个copy constructor相当于该对象的静态类型而非动态类型。任何时候,复制动作都是以对象的静态类型为本。
一个被抛出的对象(必为临时对象)可以简单地用by reference的方式捕捉,不需要以by reference-to-const的方式捕捉。但是函数调用过程中,将一个临时对象传递给一个non-const reference 参数是不允许的。
有如下三条条语句:
catch (Widget w) ...
catch (Widget& w) ...
catch (const Widget& w) ...
对于第一条语句,需要付出“被抛出物”的 “两个副本” 的构造代价。
对于第二三条语句,只需要付出 “被抛出物” 的 “一个副本” 的构造代价。
关于类型转换:
“exceptions 与 catch 子句相匹配” 的过程中,仅有两种转换可以发生。第一种是 “继承架构中的类转换”,第二种是从一个“有型指针”转为“无型指针”。
catch子句总是依出现顺序做匹配尝试,而非最佳匹配。而虚函数采用的是最佳吻合策略。因此绝不要将“针对base class而设计的catch子句” 放在 “针对derived class 而设计的 catch 子句”之前。
1 “即使从未使用任何exception处理机制,也必须付出”的最低消费额:
必须付出一些空间,放置某些数据结构(记录着哪些对象已被完全构造妥当);必须付出一些时间,随时保持那些数据结构的正确性。
2 try语句块的成本
只要用上了一个,即决定捕捉exceptions,就得付出这样的成本。初略估计,如果使用try语句块,代码大约整体膨胀5%~10%,执行速度亦大约下降这个数。这是在假设没有任何 exceptions 被抛出的情况下。
3 抛出一个exception的成本
抛出一个exception的成本十分巨大。和正常的函数返回动作比较,由于抛出exception 而导致的函数返回,其速度可能比正常情况下慢3个数量级。但是只有在抛出exception时,你才需要承受这样的冲击,而exception的出现应该是罕见的。如果exception的出现比较频繁,往往是代码设计有问题。
因此为了尽可能降低异常处理的成本,请将try语句块的使用限制在非用不可的地方,并且在真正异常的情况下才抛出exceptions。
一个程序 80% 的资源用于 20% 的代码身上。
如果软件上有性能问题,正确的做法是:尽可能地将最多的数据提供给程序分析器,让其分析出软件的瓶颈所在的那20%的代码。
eager evaluation(急式评估)
可以做到的常见四点:
可以通过over-eager evaluation如 caching 和 prefetching 等做法分期摊还预期运行成本。
1 “为了让函数调用成功” 而产生的临时对象。
发生于“传递某对象给一个函数,而其类型与它即将绑定上去的参数类型不同”的时候。
有如下代码:
size_t countChar(const string& str, char ch);
char buffer[MAX_STRING_LEN];
char c;
cin >> c >> setw(MAX_STRING_LEN) >> buffer;
cout << "There are " << countChar(buffer, c)
<< " occurrences of the character " << c
<< " in " << buffer << endl;
countChar的调用动作,第一个自变量是char数组,但是相应函数参数类型是const string&。因此为了调用成功,编译器会以buffer作为自变量,生成一个类型为string的临时对象。于是 str 参数会被绑定于这个string临时对象上。当countChar返回,此临时对象会被自动销毁。
只有当对象以by value方式或reference-to-const 参数时,这些转换才会发生。如果对象被传递给一个reference-to-non-const 参数时,并不会发生这样的转换,因为能够修改临时变量没有意义。
2 当函数返回一个对象时
有如下一个函数:
const Number operator+(const Number& lhs, const Number& rhs);
此函数的返回值是一个临时变量,因为它没有名称:它就是函数的返回值。
这个临时变量很难被消除。
结论:
临时对象那个很可能很消耗成本,所以应该尽可能消除它们。至少要知道哪些地方可能产生临时对象。
如果函数一定得以 by-value 方式返回对象,那么就绝对无法消除临时变量。
可以通过 return value optimization完成优化。这个需要通过网络调查下。
复合操作比其对应的独身版本效率高,因为独身版本通常必须返回一个新对象(临时变量),而复合版本直接将结果写入其左端自变量。
如果同时提供某个操作符的复合形式和独身形式,便允许你的客户在效率与便利性之间做取舍。
面临命名对象或临时对象的抉择时,最好选择临时对象。
身为库开发者,应该两者都提供;身为应用软件开发者,如果性能时重要因素的话,应该以 “复合版本” 操作符取代其 “独身版本”。
类型安全指的是什么???
virtual functions:
大部分编译器使用所谓的virtual tables 和 virtual table pointers——此二者常被简写为 vtbls 和 vptrs。
vtbl 通常是一个由 “函数指针” 架构而成的数组。某些编译器会以链表取代数组,但基本策略相同。程序中的每个class 凡声明(或继承)虚函数者,都有自己的一个vtbl,而其中的条目就是该class的各个虚函数实现体的指针。
如果derived class基础 base class,然后重新定义了某些继承而来的虚函数,并加上新的虚函数,其vtal内的条目将会指向对应于对象类型的各个适当函数,以及未被derived class重新定义的base class的虚函数。
凡声明有虚函数的class,其对象都含有一个隐藏的data member——vptr。这也意味着,每一个拥有虚函数的对象都需要付出 “一个额外指针” 的代价。
虚函数在被调用时的成本不大,和一个非虚函数的效率相当。使用虚函数等于放弃了inlining(如果虚函数通过对象被调用,倒是可以inline,但是大部分调用虚函数的动作是通过对象的指针或references完成的,这是无法被inlined的)。
在多重继承下,“找出对象内的vptrs”会比较困难,因为此时一个对象内会有多个vptrs(每个base class各对应一个),针对base classes而形成的特殊vtbls也会被生产出来,因此运行时期的调用成本也有轻微的成长。而且往往为了避免菱形继承,还会引入虚继承。virtual base classes会导致另一个成本,因为其实现做法常常利用指针,指向”virtual base class成分”,以消除复制行为,而你的对象内可能出现一个(或多个)这样的指针。
如果有这样的继承关系:
class A {...};
class B: virtual public A {...};
class C: virtual public A {...};
class D: public B, public C {...};
D对象内存布局看起来可能如下所示:
运行时期类型辨识的成本(RTTI):
所谓virtual constructors 是某种函数,视其获得的输入,可产生不同类型的对象。
可以简介将non-member functions 虚化。
非成员函数中的static对象:此对象在第一次被调用时才产生。
class中的对象:即使从未被用到,它也会被构造(及析构)
带有 private constructors 的class 不得被继承
限制能产生对象数量的class,不建议被继承。
一个用来计算对象个数的Base Class,private继承这个Base Class。
所谓static对象,不只涵盖明白声明为static对象,也包括global scope 和 namespace scope 内的对象。
为什么当对象涉及多重继承或虚拟继承的基类时,会拥有多个地址?
判断某个对象是否位于heap内
这往往是为了解决安全的delete this的问题。
可以定义一个存虚类,然后让工作类public继承它。
class HeapTracked {
public:
class MissingAddress {};
virtual ~HeapTracked() = 0;
static void *operator new(size_t size);
static void operator delete(void *ptr);
bool isOnheap() const;
private:
typedef const void* RawAddress;
static list<RawAddress> addresses;
}
list<RawAddress> 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) {
list<RawAddress>::iterator it = find(addresses.begin(), addresses.end(), ptr);
if (it != addresses.end()) {
address.erase(it);
::operator delete(ptr);
} else {
throw MissingAddress();
}
}
bool HeapTracked::isOnHeap() const {
const void* rawAddress = dynamic_cast<const void*>(this);
auto it = find(addresses.begin(), addresses.end(), rawAddress);
return it != addresses.end();
}
这样的mixin class有一个缺点,就是不能用于内建类型上,因为它们不能继承。
禁止对象产生于heap之中
??似乎做不到
借用unique_ptr后,别名过期,会销毁其别名指向的对象吗??
对基类函数调用*,访问的是父类还是子类??
167→问题??
利用member templates来转换smart pointer的技术点:
unique_ptr 可以转化为 unique_ptr吗??原理??
25的virtual copy constructor
RCObjects只能诞生于heap
编译器在const 和 non-const member functions 之间的选择,只以“调用该函数的对象是否是const“为基准。
现在式思维:
未来式思维:
将animal的assignment操作符声明为虚函数会导致”异性赋值“。
将函数声明为纯虚函数意味着:
pure virtual destructors必须被实现出来。
继承体系中的non-leaf类应该是抽象类。
数组问题???
需要先确定你的C++ 和 C编译器产生兼容的目标文件。
然后需要考虑4个方面的问题: