However, writing C++ code is already complex, and writing strongly-exception-safe code makes it even more complex.
To be fair, writing such code isn't just an issue where exceptions are used. Writing code without involving exceptions, that can just as equally fail to acquire resources for the same reasons, needs about as much work to ensure correctness.
It's not just resource handling code. The point there is kind of that if you choose to use C++ exceptions in your project, then you have to think about exception-safety in every function, not just in resource handling ones. e.g. Given a made up class with some invariant, in it's functions, we have to think about whether every other function that it calls throws or not.
void Widget::SetHealth( float value )
{
//class invariant: m_integerHealth == floor(m_realHealth)
m_integerHealth = (int)floor(value);
UpdateHealthHUD( m_integerHealth );//if this throws, the invariant is broken. Can/does it throw?
m_realHealth = value;
}
The fix for the above code is pretty simple -- update both members before calling the HUD function, but that's not the point. Given that C++98 / C++03 give you no solid way to know whether a function throws or not (C++11 fixes this somewhat), this is a mental burden that companies/projects/people have the choice of opting out from, by simply shunning C++ exceptions, which in my experience has been a common choice. Contrast this to C#, where the IDE can very quickly tell me whether a function throws, and what specific exceptions is can throw -- in that case, the burden of writing safe code is lessened.
Even some nice safe looking C++11 code like this is actually a potential memory leak, if one of the constructors throws. C++ likes to set traps like that
f( std::unique_ptr<T1>{ new T1 }, std::unique_ptr<T2>{ new T2 } ); //looks safe; can leak
And on the topic of the rule of 3, more often than defining all 3, I just do the destructor, and inherit noncopyable to prevent any chance of the other two causing trouble later.
Same here With new classes, I usually follow YAGNI, and start off by inheriting a noncopyable base unless it's obvious that clones need to be made. Also, the RO3 isn't needed for most classes, as long as you use it in your RAII smart-pointers and containers -- from that point on, the rest of your classes can be composed out of these RAII objects, meaning they don't need destructors. So, the vast majority of my classes remain non-copyable and don't have explicit destructors, which takes care of the RO3.
When using copy & swap, no needless self-assignment checks have to be performed ( if( &cp == this ) return *this; )
You may still want to include a self-assignment check, even though the code still works without one. For example, if you have 100 member variables, each of which is expensive to copy and/or swap, then this check will act as a healthy optimization.
A copy-constructor's initialisation list will execute noticably faster than manually assigning values.
Only if those members are default initialized and then later assigned. If the members are primitive types, then they're not initialized by default, so using either initialization or assignment will be exactly equivalent.
copy & swap is guaranteed to be exception safe
The standard says that std::swap should never throw... but a clumsy programmer can still write a swap function for their own class (which overrides std::swap via ADL), which breaks this rule and throws exceptions. You need to have trust in all the programmers on the project that they actually will write nothrow swap functions.