The Rule of Zero in C++ 之前没有注意到这个细节,比如析构函数实现为空或指定为defualt, 会改变类的行为,move语义就不会生效。
这个Rule的意思是,要不就全部不实现(The Rule of Zero), 要不就实现5/6个(The Rule of Five) 。
这周主要精力在都集中在C++ class, 各种构造和原理,我想我已经掌握的不错了。
If a class has a pointer member, it probably needs a user-defined or deleted destructor, copy and move. 有指针成员,可能需要用户定义这些构造函数和操作。因为指针需要删除,而按成员方式的拷贝会出错。
If a class has a destructor, it probably needs user-defined or deleted copy and move;
By default, declare single-argument constructors explicit; 单个参数的默认构造函数。$5.1.2 [CG:C.46] 有时候不期望它转换,则使用这个关键字。
Provide strong resource safety; that is, never leak anything that you think of as a resource;
If a class is a resource handle, it needs a user-defined constructor, a destructor, and non-default copy operations;
Return containers by value(relying on move for efficiency). For non-value types, such as types in an inheritance hierarchy, return the object by unique_ptr
or shared_ptr
. [CG: F.20].
如果自己定义的类型copy操作的代价昂贵,swap操作内部应该使用移动操作。
constexpr complex<double> operator""i(long double arg) // imaginary literal
{
return {0,arg};
}
C的奇技淫巧是不是太过分了? 难怪这么多人都不愿意学C,它的小性子还怪多的。
而使用指针Iterators性能更优势:This iterator model (§12.3) allows for great generality and efficiency. 还有range-for的方式其实是隐式的指针迭代使用begin()和end()。很好理解generality,至于为什么efficiency呢? 因为基于index的对sequential random access containers有效,要保证 vector.size()
是高效的操作,要保证实现了operator[](std::size_t)
。有人提到这个性能差异聊胜于无is likely negligable or none ,
因为编译器的优化。这个boy: My bad光速打脸,太搞了,不能想当然。
Also, memory is not the only resource. A resource is anything that has to be acquired and (explicitly or implicitly) released after use. Examples are memory, locks, sockets, file handles, and thread handles. Unsurprisingly, a resource that is not just memory is called a non-memory resource.
**Leaks **must be avoided in any long-running system, but **excessive resource retention **can be almost as bad as a leak. 资源的滞留和泄露一样糟糕
The compiler is obliged (by the C++ standard) to eliminate most copies associated with initialization, so move constructors are not invoked as often as you might imagine. This(copy elision) eliminates even the very minor overhead of a move. On the other hand, it is typically not possible to implicitly eliminate copy or move operations from assignments, so move assignments can be critical for performance.
在看这句话的时候没太懂,查了资料,也就懂了。主要涉及编译器copy elision
的技术。Bjarne的意思,编译器消除了initialization相关的大部分copy操作,所以move的构造函数并没有按你想象的那样被总是会被调用。这种copy elision
技术甚至把move很小的开销都消除了。但是这么好的技术,通常不会隐式在copy/move赋值的过程,所以程序员还要自己调用move。 就是说,copy elision优化了初始化过程,直接调用一次构造函数完成初始化,主要是对于没名字的对象;对于有名字且在free store的对象,会同样生成临时对象然后类型转换。但是编译器并没有优化赋值过程。更多关于copy elision的内容如下:
Copy elision是C++编译器优化代码的方式,wiki有不错的解释。其中,类对象的copy/move构造的实现可以被忽略,即便copy/move/destructor有副作用。
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects.
Consider:
Stock stock1 = Stock("", );
Stock stock2 = Stock("a", 12); // constructor,
sotck1 = Stock("a", 12); // constructor for temp obj, and copy assignment operator
也就不难理解为什么第二句代码,比第三句有更好的性能了。因为compiler把stock2和右边的对象视为同一个,并没有发生copy assignment的操作,我们就说这个copy被消除了。What are copy elision and return value optimization? 这个回答也很优秀。
move semantic,移动并不是真正的移动,而是把对象A的资源解开,绑定到对象B上面,优化性能主要在于把指向new出来的对象的指针给改了,就像房子的过户,房子没动,但是合同的上产权人名字改变了。
笔记都在纸上,没时间再到博客上补充了,太晚了。 今天状态不错。自我提升既是学习的过程,也是忘却学习的过程。 每天都应该抽出一段散步的时间,边走走,边让自己思考和消化所学习的内容。思维就会开阔了。
今天顺手在论坛上回答了一个C++ constructor的问题, 主要是因为函数重载的ambious的编译错误。这是一个不宜察觉的错误,尤其当它发生在类的构造函数上。
C++的语言特性细节太多了,以至于许多人不愿意学这个。 不写了,洗漱,写写两个编程练习题就休息了。
Donald Knuth on work habits, problem solving, and happiness ,这篇文章记录了Knuth是如何学习的.
如何学习:
阅读论文的方式:
其它:
昨晚失眠的时候突然好奇恐龙怎么灭绝的,今天想起来了这茬,就Google了一下。大概是说在白垩纪(K)-古近纪(P)之间的时期(称为K-PG),发现这个时期的地质中的钛含量异常高,而钛在小行星中比较常见。推断有发生小行星撞地球,造成了短期的生物大灭亡,75%的物种灭绝。我想说,人作为一个特殊的动物,有现在这样的文明,简直是一个奇迹。美丽的行星地球。而我们每个个体的一生, 又多渺小而微不足道。
If you want to be explicit about generating default implementations, you can:
class Y {
public:
Y(Sometype);
Y(const Y&) = default; // I really do want the default copy constructor
Y(Y&&) = default; // and the default move constructor
// ...
};
When a class has a pointer member, it is usually a good idea to be explicit about copy and move operations. The reason is that a pointer may point to something that the class needs to delete
, in which case the default memberwise copy would be wrong. Alternatively, it might point to something that the class must not delete
. In either case, a reader of the code would like to know.
简单翻译下:当类有一个指针成员,最好显示地移动或者拷贝。因为指针可能指向了类需要销毁(delete)的资源或者对象,默认的按成员拷贝的方式会出错。即便不需要delete, 阅读代码的人也该知道(就是说,也要显示的default)。很好理解,因为虽然进行了拷贝和移动,然而有的资源仍是处于共享的状态。
#### 拇指规则(rule of thumb) 又称经验规则,rule of zero。accu这篇文章写得不错: ENFORCING THE RULE OF ZERO 。It states that if a class defines a destructor it should almost always define a copy constructor and a* copy assignment operator*. 这是个好习惯,应该定义每个C++类的时候都该保持这个习惯,虽然编译器不强制我这这么做。这篇这个文章的那个Managed resources部分的List 2代码写得好秀,作者设计的这个类不允许拷贝,但允许移动。List3的代码开始没看懂,看完这篇unique_ptr 自定义deleter的写法也就懂了,主要是不熟悉这种写法。List3的deleter不是自定义的,下面还有自定义的写法:
struct Widget{ };
// ...
auto deleter = []( Widget *p ) {
cout << "delete Widget!" << endl;
delete p;
};
unique_ptr<Widget, decltype(deleter)> ptr{ new Widget, deleter };
rule of thumb的具体体现的意义:Polymorphic deletion / virtual functions
!!!明天再写,熬不住了,睡觉了
decltype (C++) ,获取变量/函数/成员的类型或者表达式值类别,内括号会导致表达式求值。
我有注意到一个英语的习惯表达,术语/词语的“发明”通常用“coined by sb”,不是什么invent之类的。
不容易,终于写完了这篇C++的左值和右值,几千字不到,写了好几天。写完也搞清楚了C++的很多东西,包括移动语义。明天继续读书。
刚顺手在StackOverflow上面回答了一个关于C++的问题 What expressions create xvalues? ,写得简单的英文,应该没有语言错误。
写了一点东西,解释了C++“具有身份”和“移动”的语义,分别这两篇是:
要不是英语写的不如中文准确,我都想用英文写了。还得多练习和使用英语!
今天在写这篇C++的左值和右值 ,当是笔记吧。写东西好费脑子,查资料也很费时间。拖到晚上11点了,还没写完,困了就休息。感觉还是不够专注,特别是下午容易疲惫。
忙一些杂事去了,没学多少东西,笔记就不写了。
小结: 读完了 A Tour of C++的Class章节,涵盖了具体类,抽象类,虚函数这些知识。遇到问题记在纸上,阅读结束再查资料,效率高了许多。
虚函数的匹配机制
当函数调用Virtual Functions时,多个派生类(或者说子类)都实现了基类的虚函数,编译器要通过vtbl(virtual function table)纯虚函数表去选择相应的函数,才能保证不会调用出错。
虚函数调用的效率:
使用override的习惯
派生类对虚函数进行override的时候,最好显式的使用override
关键字,以避免拼写错误或者让代码更明显。
抽象类的灵活性与不足:抽象类提供的函数,可以简化派生类的函数实现。抽象类函数,甚至不需要知道实参的类型,通过引用或指针就能完成相应的操作。强大的灵活性,唯一的不足就是依赖引用或者指针。
警惕抽象类的资源泄露:由于抽象类灵活的特性,在派生类的destructor函数要保证资源能被释放,这太重要了。
英语固定搭配: especially **with respect to **error handling(固定搭配with respect to=就..而言/对于)
dynamic_cast运算符,有助于判定对象是否属于某个抽象类的类型,有点像Python的is
, A是B的一个实例吗?。是一个问询,可以简化代码。如果无法转换向目标类时,想要它报错,就用dynamic_cast
作用于引用类型;不希望报错就,就作用于指针类型。
unique_ptr:new出来的“裸”指针,直接从函数返回是很危险的。最好用标准库的unique_ptr
,例如return unique_ptr<Shape>{new Circle{p,r}};
C++编程建议:
cons
t;dynamic_cast
where class hierarchy navigation is unavoidable;类层次漫游行为不可避免时,使用它dynamic_cast
unique_ptr
or shared_ptr
to avoid forgetting to delete objects created using new;