CPP Idioms
1. The Rule of Three
The Rule of Three is a rule of thumb for C++, basically saying, if your class needs either:
- a copy constructor,
- an assignment operator,
- or a destructor,
then it is likely to need all three of them.
The default semantics for these three member functions are:
- Destructor: Call the destructors of all the object's class-type members
- Copy constructor: Construct all the object‘s members from the corresponding members of the copy constructor’s argument, calling the copy constructors of the object's class-type members, and doing a plain assignment of all non-class type (e.g., int or pointer) data members
- Copy assignment operator: Assign all the object‘s members from the corresponding members of the assignment operator’s argument, calling the copy assignment operators of the object's class-type members, and doing a plain assignment of all non-class type (e.g., int or pointer) data members.
Reference:
- http://en.wikipedia.org/wiki/Rule_of_three_%28C%2B%2B_programming%29
- http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three?rq=1
2. Copy-and-Swap
The copy-and-swap idiom is the solution, and elegantly assists the assignment operator in achieving two things:
- avoiding code duplication with copy constructor;
- providing a strong exception guarantee;
1 2 3 4 5 6 7 8 | // traditional T& operator=(const T& rhs) { T temp(rhs); // create temporary local data of the data, may throw exception swap(*this, temp); // swap the old data with the local new data return *this; // temporay local data destructs with old data destruct } |
Copy-and-swap idiom need three things:
- a working copy-constructor
- a working destructor
- a non-throwing swap function.(should not use
std::swap()
), more about this see Effective CPP Q25
So as to The Rule of Three, the assignment operator can be write in form automatically by adding a swap function.
If you're going to make a copy of something in a function, let the compiler do it in the parameter list.
1 2 3 4 5 6 | // better T& operator(T rhs) // don't need enter the function if construction of the copy fails { swap(*this, rhs); return *this; } |
Reference:
3. Pointer to implementation (Pimpl)
The pimpl idiom is a modern C++ technique to hide implementation, to minimize coupling(耦合), and to separate interfaces. This technique is described in Design Patterns as the Bridge pattern. It is sometimes referred to as:
- “handle classes”,
- the “Pimpl idiom” (for “pointer to implementation idiom”),
- “Compiler firewall idiom”
- or “Cheshire Cat”,
especially among the C++ community.
Here's how the pimpl idiom can improve the software development lifecycle:
- Minimization of compilation dependencies.
- Separation of interface and implementation.
- Portability.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // .h class Handler { public: Handler(); void foo1(); int foo2(); Handler(const Handler& other); Handle& Handle::operator=(const Handle &other); private: class HandlerImpl; // 私有,对用户隐藏此类 std::tr1::unique_ptr<HandlerImpl> pimpl; // 用于深拷贝,shared_prt用于浅拷贝 }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // .c class Handler::HandlerImpl { public: ... HandlerImpl() {...} // 修改函数实现,不需要重新编译用户代码 HandlerImpl(const HandlerImpl&) {...} void foo1() {...} int foo2() {...} ... }; Handler::Handler() : pimpl(new HandlerImpl) { ... } Handler::Handler(const Handler& other) : pimple(new HandlerImpl(*(other.pimpl))) // 深拷贝 { ... } Handler& Handler::operator=(Handler other) { swap(*this, other); return *this; } void Handler::foo1() { pimpl->foo1(); } int Handler::foo2() { return pimpl->foo2(); } |
Reference:
- http://en.wikipedia.org/wiki/Opaque_pointer
- http://c2.com/cgi/wiki?PimplIdiom
- http://msdn.microsoft.com/en-us/library/vstudio/hh438477.aspx
Copy on Write (COW)
On std::string
RAII
NRV(Named Return Value)/RVO(Return Value Optimization)
One Definition Rule (ODR)
Compiler and Name
C/CPP编译期间不一定需要查看函数的声明。对于未声明的函数,编译器可以采用“隐式函数声明(implicit declaration of function)”:编译器认为未声明的函数都返回int,并且能接受任意个数的int型参数。如果声明了函数,则需要进行参数检查。而链接期间一定会做参数检查。
CPP从C那里继承了单遍编译的特点,编译器只能根据目前看到的代码做出决策,后面读到的代码也不会影响前面做出的决定。这也影响了名字查找(name lookup)和函数重载决议。对于一个名字/符号,CPP只能通过解析之前源码来了解名字的含义,而不是像Java那样读取类的元数据获得信息。对于函数重载决议,当CPP编译器读到一个函数调用语句时,它必需(也只能)从目前已看到的同名函数中选出最佳函数,即使后面还有更适合的函数也不影响当前决定(对于class成员函数来说,全体同名函数同会参与重载决议)。
对于函数来说,它的前向声明(forward declaration)就是函数的接口/原型声明。
对于class来说,它的前向声明就是class名的声明class foo;
。
有时候class也需看到完整定义,比如需要访问class成员时或需要知道class大小时。
data abstract vs object-oriented
Data Abstract: Abstract Data Type(ADT), 值语义。拷贝时是内存拷贝。很像Object-based. Object-oriented: 对象/引用语义。拷贝是引用拷贝。三大特征:封装,继承,多态。 Object-based: 对象/引用语义。特征:封装,没有继承和多态。即只有具体类,没有抽象接口。