Three cheers for writing documentation
Last week I thought I'd finished implementing
Improving shared_ptr for C++0x for
GCC: GNU Compiler Collection but have spent the
last few days writing more tests and docs
on the implementation.
Writing the docs resulted in far larger code changes than the
additional testing with funky types and debug allocators.
Testing found that I was using the wrong
allocator type in one place (so potentially leaking
memory, not good
for a shared_ptr!) and that I needed to remove a
const-qualifier somewhere else, easy with this shiny new
std::remove_const meta-function. Only about 5 lines
to change. Documenting the
"finished"
design resulted in several simplifications, a better
interface, and the complete removal of some unnecessary code.
The difference is partly because my original tests covered
most cases,
but it still makes me think of
Richard
Feynman's point that if you can't explain something in
simple terms, you don't really
understand it. (The much paraphrased original seems to be
"I couldn't reduce it to the freshman level. That means we
really don't understand it.") Explaining why I'd made
certain compromises in the design
embarassed me into fixing them, and documenting the unresolved
issues made me decide to resolve some of them and give a
decent rationale for leaving the others.
I wish I could give the same amount of time to designing,
documenting and refactoring some of the code I'm paid to
write, but my open-source dabbling benefits from having no
deadlines. The shared_ptr changes have been brewing for
months. The downside is that some of my
PStreams ideas have been idling on a hard disk
for years. I really must fix showmanyc.
Cool new C++ feature
C++0x introduces the following function:
template<class T, class... Args>
shared_ptr<T> make_shared(Args&&... args);
Which is used like so:
class A {
A(int);
// ...
};
shared_ptr<A> p = make_shared<A>(99);
// better than: shared_ptr<A> p(new A(99));
This function is cool for two reasons.
First, it only needs a single allocation for the A
and the shared_ptr's internal bookkeeping.
Secondly, it gives the strong exception-safety guarantee.
Consider:
void f(shared_ptr<A>, shared_ptr<A>);
f(shared_ptr<A>(new A(1)), shared_ptr<A>(new A(2)));
Because C++ doesn't define the order of evaluation here, the
compiler is allowed to perform both
new operations
before calling either shared_ptr constructor. If the second
allocation or construction throws an exception, the first
object will be leaked.
Today, the usual way to avoid the leak is to avoid calling
new twice in that one statement (using a local
variable instead of one of the temporaries,) but in The
Future we will
be able to say:
f(make_shared<A>(1), make_shared<A>(2));
This cannot leak, because both
A objects are safely
managed
by a shared_ptr as soon as they're constructed. RAII to the
maaaaaaaax.
So it's cool because it's safer and more efficient.
It's one less reason to see naked new delete
operators in C++ code.
make_shared() is possible as a library in C++03, but
with variadic templates and rvalue-references in C++0x it's
really cool. Try it out in GCC 4.3 soon (I hope.)