A collection of most wanted and widely accepted idioms and coding conventions for C++ development presented along with examples and comments. The lecture targets performance oriented codes so emphasis is on performance-friendly techniques.
◼ Bjarne Stroustrup’s C++ Style and Technique FAQ [www.stroustrup.com/bs_faq2.html] ◼ stackoverflow.com, C++faq tag ◼ Microsoft Channel 9 [http://channel9.msdn.com/] ◼ Scott Meyers, Effective C++ ◼ cppreference.com ◼ Bjarne Stroustrup, The C++ Programming Language Federico Ficarelli, Idiomatic C++ 3
release, we need to keep track of: ◼ natural return; ◼ return statements; ◼ exceptions thrown. Issues: ◼ code complexity; ◼ duplicated code (copy and paste); ◼ forces to catch and re-throw (even if we aren’t able to handle it); ◼ error prone. void foo () { char* ch = new char[100]; if (...) if (...) return; else if (...) if (...) else throw "ERROR"; // This may not be invoked... delete [] ch; } void bar () { lock.acquire(); if (...) if (...) return; else throw "ERROR"; // This may not be invoked... lock.release(); } Federico Ficarelli, Idiomatic C++ 5
<class T> class AutoDelete { public: AutoDelete (T * p = 0) : ptr_(p) {} ~AutoDelete () throw() { delete ptr_; } private: T *ptr_; DISALLOW_COPY_AND_ASSIGN(AutoDelete); }; class ScopedLock { public: ScopedLock (Lock & l) : lock_(l) { lock_.acquire(); } ~ScopedLock () throw () { lock_.release(); } private: Lock& lock_; DISALLOW_COPY_AND_ASSIGN(ScopedLock); }; Exception Safety Rule: the only code that is guaranteed to be executed after an exception is thrown are the destructors of objects residing on the stack. Federico Ficarelli, Idiomatic C++ 6
X()); if (...) if (...) return; // No need to call delete here. // Destructor will delete memory } void bar() { ScopedLock safe_lock(l); if (...) if (...) throw "ERROR"; // No need to call release here. // Destructor will release the lock } RAII: every time you need to wrap and manage a resource (memory, file, lock, etc...) in a class, let the constructor acquire and the destructor release it: the stack semantics will release the resource when it leaves the scope. Federico Ficarelli, Idiomatic C++ 7
a single, clear responsibility and that responsibility should be entirely encapsulated by the class. Constructor: it must acquire the managed resource and, in case of failure, raise a proper exception. The acquisition process must be RAII itself (without relying on the destructor). Destructor: it starts with a valid and constructed object (guaranteed by constructor) and must release the resource. It cannot fail. Federico Ficarelli, Idiomatic C++ 9
◼ each resource type needs a proper resource holder; ◼ strict ownership, resources cannot be (easily and cleanly) passed around. Federico Ficarelli, Idiomatic C++ 10
ownership in C++? Ownership Semantics: an object owns a resource when it has the responsibility to release that resource. struct Resource { void foo() { /* ... */} }; Resource* get_resource() { return new Resource; // Ownership implicitly transferred to the caller } int main (void) { get_resource()->foo(); // ? } // Header file // ... // AMAZING documentation about // the following function. Resource* get_resource(); // ... Federico Ficarelli, Idiomatic C++ 11
when the owner goes out of scope, raw ptr is destroyed (operator delete); ◼ we can release ownership and take back the raw ptr. Strict Ownership, Transfer Allowed: the auto_ptr has semantics of strict ownership, meaning that there is only one auto_ptr instance responsible for the object's lifetime. If an auto_ptr is copied, the source loses the reference. #include <memory> int main() { std::auto_ptr<T> pt( new T ); } // <-- ~T() #include <memory> int main() { T* pt1 = new T; // pass ownership to an auto_ptr std::auto_ptr<T> pt2( pt1 ); *pt2 = 12; // same as "*pt1 = 12; pt2->SomeFunc(); // same as "pt1->SomeFunc(); // use get() to see the pointer value assert( pt1 == pt2.get() ); // use release() to take back ownership T* pt3 = pt2.release(); // no owner, no auto-delete! delete pt3; } Federico Ficarelli, Idiomatic C++ 13
); pt.reset( new T(2) );// ~T(1), owns T(2) } // ~T(2) int main() { auto_ptr<T> pt1( new T ); // pt1 owns auto_ptr<T> pt2; // pt2 non-owning pt1->DoSomething(); // ok pt2 = pt1; // pt1 -> pt2 pt2->DoSomething(); // ok pt1->DoSomething(); // !!! } // ~T() ◼ Ownership can be explicitly dropped and set on the fly (reset); ◼ a non-owning auto_ptr has the same semantics as NULL pointer: never dereference it (check with operator bool). Federico Ficarelli, Idiomatic C++ 14
allocated with operator new only; ◼ copying and assigning changes the owner of a resource, modifying not only the lhs but also the rhs, which breaks assignment semantics; ◼ cannot be used in stl containers. std::vector< std::auto_ptr<T> > v; /* ... */ std::sort( v.begin(), v.end() ); // ? Federico Ficarelli, Idiomatic C++ 15
supports custom deleter; ◼ useful for automatic deletion of local objects or class members (PIMPL, RAII, etc...) ◼ can be “simulated” using the const auto_ptr Idiom. Strict Ownership, Transfer Not Allowed: the scoped_ptr has semantics of strict ownership, meaning that there is only one scoped_ptr instance responsible for the object's lifetime. The owning scoped_ptr cannot be copied, ownership cannot be transferred. const auto_ptr<T> pt1( new T ); auto_ptr<T> pt2( pt1 ); // illegal auto_ptr<T> pt3; pt3 = pt1; // illegal pt1.release(); // illegal pt1.reset( new T ); // illegal Federico Ficarelli, Idiomatic C++ 16
multiple ownership, meaning that multiple owning instances are allowed at a time. The instance is reference counted*: it will be destroyed only when the last owner is released. ◼ Useful when object’s lifetime is complex and not tied to a particular scope/object; ◼ supports custom deleter; ◼ can be safely used inside stl containers. Federico Ficarelli, Idiomatic C++ 17
◼ use any type of smart ptr (depending on your needs); ◼ improves robustness dramatically. // Header file // ... // AMAZING documentation about // the following function. std::auto_ptr<Resource> get_resource(); // ... struct Resource { void foo() { /* ... */} /* ... */ }; std::auto_ptr<Resource> get_resource() { return std::auto_ptr<Resource>( new Resource ); // Ownership EXPLICITLY transferred // to the caller } int main (void) { get_resource()->foo(); // ~Resource() } Resource Return Idiom: never return raw pointers from within functions; prefer conveying resource ownership explicitly in the return type. Federico Ficarelli, Idiomatic C++ 21
written to: ◼ be self-sufficient; ◼ be portable; ◼ minimize dependencies; ◼ avoid pollution of client’s names search space. Federico Ficarelli, Idiomatic C++ 22
IDIOMATICPP_DATE_INL_H_16032013 #include <muslimdate.h> #include <cmath> // <-- #include <date.h> INLINE void Date::Convert(MuslimDate* other) { // Some heavy work... double res = std::exp(somevalue); // <-- // ... *other = MuslimDate(/*...*/) } INLINE int Date::get_month() { return month_; } INLINE int Date::get_day() { return day_; } INLINE int Date::get_year() { return year_; } #endif // IDIOMATICPP_DATE_INL_H_16032013 ◼ Collect all inline definitions in a separate header; ◼ beware of C std headers; ◼ no using statements (qualify all identifiers); ◼ be sure that the inline header is made of inline definitions only. Federico Ficarelli, Idiomatic C++ 25
decoupling; ◼ the main goal is to reduce dependencies and build time; ◼ accepted downsides could impact performance. Federico Ficarelli, Idiomatic C++ 27
◼ invokes implementation of an abstraction/class using runtime polymorphism; Dependency Inversion Principle: implementation classes should not depend on each other. Instead, they should depend on common abstraction represented using an interface class. Federico Ficarelli, Idiomatic C++ 28
but not invisible; ◼ idiom meant to completely decouple interface (and clients) from implementation; ◼ implements a true compilation firewall; ◼ consider carefully the advantages (build time, insulation) and downsides (extra indirection level). Federico Ficarelli, Idiomatic C++ 30
the function cannot depend non- public members (“Don’t give away your internals”); ◼ breaks apart monolithic classes to liberate separable functionalities, reducing coupling. Function Placement: when implementing new functionalities, prefer non-member non-friend functions. When implementing functionalities, the common belief is that OO prefers members. Federico Ficarelli, Idiomatic C++ 32
make f a member function of C; else if (f is operator>> or operator<<) { make f a non-member function; if (f needs access to non-public members of C) make f a friend of C; } else if (f needs type conversions on its left-most argument) { make f a non-member function; if (f needs access to non-public members of C) make f a friend of C; } else if (f can be implemented via C's public interface) make f a non-member function; else make f a member function of C; Functions: the Meyer’s Algorithm [Effective C++] Federico Ficarelli, Idiomatic C++ 34
objects. Use explicit RAII and smart pointers to expose ownership and enforce exception safety. 2. Give one entity one cohesive responsibility. 3. Keep your header files clean, don’t harm clients (nor yourself). 4. PIMPL and DIP judiciously. 5. Don’t optimize prematurely: correctness, simplicity and clarity come first. 6. Don’t pessimize prematurely. Federico Ficarelli, Idiomatic C++ 35
construction, ◼ copy construction; ◼ copy assignment, ◼ destruction. Pay attention: ◼ compiler can generate them for you; ◼ the language treats classes with value semantics by deafult. Federico Ficarelli, Idiomatic C++ 37
is the most safe and widespread technique; ◼ the «init method» technique is unsafe and breaks RAII and all the idioms discussed in this chapter. Failing Constructors: “You should throw an exception from a constructor whenever you cannot properly initialize (construct) an object. There is no really satisfactory alternative to exiting a constructor by a throw”. [Stroustrup’s C++ FAQ] Federico Ficarelli, Idiomatic C++ 38
the copy-assignment operator in order to have a correct value semantics; ◼ operator= is much more hard to implement in a robust way than the copy constructor: ▪ must handle an already constructed object; ▪ in case of failure, it must leave the object in the previous consistent state (rollback). The copy-assignment operator must be transactional. Federico Ficarelli, Idiomatic C++ 42
{ std::swap(first.mSize, second.mSize); // throw() std::swap(first.mArray, second.mArray); // throw() } dumb_string& operator=(const dumb_string& other) { dumb_array temp(other); swap(*this, temp); // <-- return *this; } dumb_string& operator=(dumb_string other) // <-- { swap(*this, other); // <-- return *this; } Pass by Value: enables Copy Elision Optimization ◼ Enables strong exception-guarantee (especially the RVO version); ◼ enables type to be used with a large number of idioms. Rule-of-Three-and-a-half: whenever it makes sense, provide a no-fail swap. Federico Ficarelli, Idiomatic C++ 45
deallocation (e.g.: operator delete) and swap functions attempt shall succeed: never allow an error to be reported from within them. They are the foundation of transactional programming: without their resilience, no-fail rollback is impossible to implement. Federico Ficarelli, Idiomatic C++ 46
Rule-of-three(and-a-half) to obtain correct and robust value semantics for complex types; ◼ «None-of-three» for POD/aggregates (let the compiler generate them for you); ◼ explicitly disable copy-construction and copy assignment. Federico Ficarelli, Idiomatic C++ 47
enforce the Interface Principle, ADL/Koenig Lookup was added for this reason. Interface Principle: for a class T, all functions (including non-member) that both "mention" T and are "supplied with" T in the same namespace are logically part of T, because they form part of T's interface. Federico Ficarelli, Idiomatic C++ 49
lookup of an unqualified name fails to find a matching class member function. “The set of declarations [...] considered for resolution of the function name is the union of the declarations found by normal lookup with the declarations found by looking in the set of namespaces associated with the types of the function arguments”. [C++03, 3.4.2] // using namespace std; std::cout << "hello" << std::endl; Federico Ficarelli, Idiomatic C++ 50
void f(); }; } namespace ops { T operator+( const T&, const T& ); } int main() { // using ops::operator+; type::T a, b; a.f(); type::T c = ops::operator+(a, b); } namespace ns { class T { public: void f(); }; T operator+( const T&, const T& ); } int main() { ns::T a, b; a.f(); ns::T c = a + b; } Interface Principle, corollary: keep a type and its non-member function interfaces in the same namespace. Federico Ficarelli, Idiomatic C++ 51
unwanted ADL. «Dual» Interface Principle: avoid putting non-member functions that are not part of the interface of a type T into the same namespace as T, and especially never put templated functions or operators into the same namespace as a user- defined type. Federico Ficarelli, Idiomatic C++ 53
the headers inclusion order. // f1.h namespace A { int f(double); } // g.h namespace B { using A::f; void g(); } // f2.h namespace A { int f(int); } // g.cpp B::g() { f(1); // <-- ? } Namespace Using Principle: avoid putting using namespace directives before any inclusion directive. Since the inclusion ordering is out of implementor’s control and depends on client’s implementation, never put using namespace directives inside header files. Federico Ficarelli, Idiomatic C++ 54