[Development] Upgrading the sources to C++11 keywords (Q_NULLPTR, etc.)

Marc Mutz marc.mutz at kdab.com
Sun Feb 8 14:28:03 CET 2015


Hi,

Sorry for being late, didn't see the thread before.

On Thursday 08 January 2015 23:33:34 Thiago Macieira wrote:
> I think it's time to institute a policy that we should fix our sources to
> use the new C++11 keywords. I'd like to propose the following.

I totally agree, with the following amendments:

1. override - before adding an overriding function to a class, add
   Q_DECL_OVERRIDE to all members in a separate commit, to avoid said
   warning.

2. noexcept/nothrow - the only difference between the two is that nothrow may
   expand to throw() on compilers that implement semantics close to noexcept
   for that, and not the C++98 standard behaviour. This is currently only the
   case for MSVC, even though I believe GCC has a switch to emulate MSVC here.
   The semantic difference currently, is: when you violate noexcept, you're
   getting C++11 behaviour (std::terminate is called) or the exception leaves
   the function. If you violate nothrow, you're enterin undefined bahaviour.
   So only use nothrow if functions _cannot possibly_ throw. If you want to
   say "I'm fine with errors in this function terminating the process", which
   you should be very carefully considering (it should be the exception), you
   must use noexcept instead. Obviously, if you need conditionally-noexcept,
   you must use noexcept_expr.

   Talking about warnings: there's -Wnoexcept, which warns when a
   conditionally-noexcept function turns noexecpt(false) because of a function
   that isn't marked noexcept and where the compiler can prove it doesn't
   throw. That's a bit of a mouthful, but this, too, should be added to the
   headersclean check.

   Talking about narrow contracts: A function has a narrow contract if it has
   preconditions (on it's arguments, or *this). If you have preciditions, you
   may, in debug mode, assert them as an aid to the user of your function.
   Assertions may be turned by the user (there's also a movement behind John
   Lakos, yes, _the_ John Lakos that essentially gave us the d-pointer, to
   make this standard functionality), into exception throwing (which I had
   personally good experience with during Kleopatra development). But if your
   users do this, they expect to receive those exceptions, and not terminate
   the program without a hint that an assertion was triggered).

   That's why functions with narrow contracts should not be noexcept.

   Aside: of course, you can often drop preconditions by tightening the
   interface of the function. E.g. instead of taking a naked pointer, you
   could take a non_null_ptr<T>, which would explode when constructed with a
   nullptr, thus making sure that every successfully constructed instance is
   actually representing a non-null pointer. Another technique is to use
   enums, because the standard says that you cannot load a value from an enum
   variable that does not correspond to one of the enumerated values. Doing
   otherwise constitutes undefined behaviour, and compilers are getting oh-so-
   good at exploiting UB that your only chance is to learn about and avoid
   them.

3. nullptr - On top of the warning, which I wasn't aware about, I find the
   code easier to read. It's a mouthful, but it's what everyone will be using
   five years from now, so we might as well start it now. I treat this as a
   whitespace error, meaning I correct it whenever I touch a line of code for
   unrelated changes.

I would add the following, unrelated to C++11, but found all over the place in 
Qt, and it's next to impossible to root out: Algorithmic ineffciency. That's a 
large blob, but the most important instances of it are:

a. Not marking types as movable or primitive. We might actually want to have
   a policy to mark complex types explicitly as complex, to allow easier
   fixing of missing declarations.
   The rule here should be that every new class or struct that may at some
   point be held in a container must have a q_declare_typeinfo. Rationale:
   it's impossible to add them after release, since changing the typeinfo
   changes the memory layout of QList, making the change BIC.

b. Using QLists when they are not optimally efficient. For a QList to be
   optimally efficient, the type T must be movable (or primitive) and sizeof T
   == sizeof (void*) (yes, different on 32 and 64-bit platforms!). For a QList
   to be acceptable (actually, it's not, but it's at least not horribly
   inefficient), replace == sizeof(void*) with <= sizeof(void*).
   The rule should be that you need to accompany any use of QList that isn't
   already mandated by existing APIs (QList<QVariant>, say), by a
   static_assert that QList is optimally efficient. There are patches in the
   pipeline to make this easier than checking QTypeInfo yourself.

   Of course, when QList is not efficient, you should use a QVector instead.

c. Using QMap. As Alex Stepanov put it: every use of a map should be discussed
   in a face-to-face meeting with your manager. Since we don't have that, I'd
   change this to: Everyone wishing to use a QMap should implement one before
   using it for the first time. Then you'd see what you inflict on the world.

   In the vast majority of cases, a sorted vector is much faster, due to
   locality-of-reference. A RB-tree is optimized for wildly mixing insertions,
   lookups, and removals. When you can batch updates and separate them from
   lookups, time-wise, then a sorted vector is usually preferrable.

d. Algorithmic complexity. Avoid O(n²) like the plague. Anthing worse than
   that should get a big fat comment saying why it's necessary (like: this
   optimisation algorithm is equivalent to the knapsack problem, so I need to
   use this exponential algorithm, because a) we need the global optimum, not
   a local one, and b) the set is always less than four elements.

I could go on and on with this. Like using a set for de-duplication, then 
converting to a list and sorting that (because, surprise, QSet is 
std::unordered_set), but there's really no substitute to understanding what 
you're doing and no set of rules, however large, will give you that. Using std 
algorithms would, though, as far as they go.

I'm sorry, this has become so much longer than planned...

Thanks for reading up to here. May your code be the better for it,
Marc

-- 
Marc Mutz <marc.mutz at kdab.com> | Senior Software Engineer
KDAB (Deutschland) GmbH & Co.KG, a KDAB Group Company
www.kdab.com || Germany +49-30-521325470 || Sweden (HQ) +46-563-540090
KDAB - Qt Experts - Platform-Independent Software Solutions



More information about the Development mailing list