I built some test cases using unique_ptr and ran into some issues pretty much immediately.
Everywhere I wanted to replace was not a raw pointer T* but a Try<T*>. The factory methods that were used to create these already return a Try<T*>, which means we'd need to change those to return Try<std::unique_ptr<T>> which is intrusive. Further, Try itself doesn't readily support a std::unique_ptr as a template parameter due to using heap allocations and copy constructors internally. I think Jie's idea to use unrestricted union may help here though we'll have to wait for g++-4.6. On Fri, Dec 12, 2014 at 3:01 PM, Dominic Hamon <[email protected]> wrote: > > Great idea. > > There is one place that immediately springs to mind that I think is > trivial but will get us used to the idea and exercise some of the demons: > Unit tests. Within a test we regularly create some object on the heap and > then delete it at the end of the test, or for the lifetime of a test > fixture. All of these should be well scoped in lifetime and are prime > targets for unique_ptr. > > On Wed, Dec 10, 2014 at 3:32 PM, Jie Yu <[email protected]> wrote: >> >> Dominic, >> >> Thank you for brining this up! Instead of making a decision based on high >> level design and coding philosophy, I would really love to see some >> concrete examples in our code base where we can start to use unique_ptr >> and >> why it's better. Thoughts? >> >> - Jie >> >> On Wed, Dec 10, 2014 at 3:20 PM, Dominic Hamon <[email protected]> >> wrote: >> >> > Hello! >> > >> > We have access to std::unique_ptr, as many of you know, but we're not >> using >> > it. I'd like to start using it. Before we do, I'd like to discuss how we >> > use it, when we use it, and things to be aware of. >> > >> > The style guide has the following to say (long quote, sorry): >> > >> > " >> > Prefer to have single, fixed owners for dynamically allocated objects. >> > Prefer to transfer ownership with smart pointers. >> > >> > "Ownership" is a bookkeeping technique for managing dynamically >> allocated >> > memory (and other resources). The owner of a dynamically allocated >> object >> > is an object or function that is responsible for ensuring that it is >> > deleted when no longer needed. Ownership can sometimes be shared, in >> which >> > case the last owner is typically responsible for deleting it. Even when >> > ownership is not shared, it can be transferred from one piece of code to >> > another. >> > >> > "Smart" pointers are classes that act like pointers, e.g. by overloading >> > the * and -> operators. Some smart pointer types can be used to automate >> > ownership bookkeeping, to ensure these responsibilities are met. >> > std::unique_ptr is a smart pointer type introduced in C++11, which >> > expresses exclusive ownership of a dynamically allocated object; the >> object >> > is deleted when the std::unique_ptr goes out of scope. It cannot be >> copied, >> > but can be moved to represent ownership transfer. std::shared_ptr is a >> > smart pointer type that expresses shared ownership of a dynamically >> > allocated object. std::shared_ptrs can be copied; ownership of the >> object >> > is shared among all copies, and the object is deleted when the last >> > std::shared_ptr is destroyed. >> > >> > It's virtually impossible to manage dynamically allocated memory without >> > some sort of ownership logic. >> > Transferring ownership of an object can be cheaper than copying it (if >> > copying it is even possible). >> > Transferring ownership can be simpler than 'borrowing' a pointer or >> > reference, because it reduces the need to coordinate the lifetime of the >> > object between the two users. >> > Smart pointers can improve readability by making ownership logic >> explicit, >> > self-documenting, and unambiguous. >> > Smart pointers can eliminate manual ownership bookkeeping, simplifying >> the >> > code and ruling out large classes of errors. >> > For const objects, shared ownership can be a simple and efficient >> > alternative to deep copying. >> > Ownership must be represented and transferred via pointers (whether >> smart >> > or plain). Pointer semantics are more complicated than value semantics, >> > especially in APIs: you have to worry not just about ownership, but also >> > aliasing, lifetime, and mutability, among other issues. >> > The performance costs of value semantics are often overestimated, so the >> > performance benefits of ownership transfer might not justify the >> > readability and complexity costs. >> > APIs that transfer ownership force their clients into a single memory >> > management model. >> > Code using smart pointers is less explicit about where the resource >> > releases take place. >> > std::unique_ptr expresses ownership transfer using C++11's move >> semantics, >> > which are relatively new and may confuse some programmers. >> > Shared ownership can be a tempting alternative to careful ownership >> design, >> > obfuscating the design of a system. >> > Shared ownership requires explicit bookkeeping at run-time, which can be >> > costly. >> > In some cases (e.g. cyclic references), objects with shared ownership >> may >> > never be deleted. >> > Smart pointers are not perfect substitutes for plain pointers. >> > If dynamic allocation is necessary, prefer to keep ownership with the >> code >> > that allocated it. If other code needs access to the object, consider >> > passing it a copy, or passing a pointer or reference without >> transferring >> > ownership. Prefer to use std::unique_ptr to make ownership transfer >> > explicit. For example: >> > >> > std::unique_ptr<Foo> FooFactory(); >> > void FooConsumer(std::unique_ptr<Foo> ptr); >> > Do not design your code to use shared ownership without a very good >> reason. >> > One such reason is to avoid expensive copy operations, but you should >> only >> > do this if the performance benefits are significant, and the underlying >> > object is immutable (i.e. std::shared_ptr<const Foo>). If you do use >> shared >> > ownership, prefer to use std::shared_ptr. >> > >> > Do not use scoped_ptr in new code unless you need to be compatible with >> > older versions of C++. Never use std::auto_ptr. Instead, use >> > std::unique_ptr. >> > " >> > >> > Now I'm a big fan of explicit ownership and moving ownership rather than >> > sharing non-smart pointers, but I recognise that in our code-base, >> > ownership is difficult to reason about in many cases. However, we do >> have >> > quite a few cases where we manage lifetime scope with explicit delete >> calls >> > and I'd like to start by eliminating those. Ie, using std::unique_ptr to >> > manage lifetime not necessarily ownership. >> > >> > This is difficult though as we may pass these pointers to other methods >> or >> > even other libprocess processes. In cases that we can reason about the >> > lifetime of the various pointers, that should be fine, but we have to be >> > careful. >> > >> > One option is to start by replacing these pointers with process::Owned. >> The >> > downside to this approach is that it introduces more non-standard types >> > and, because Owned is implemented using std::shared_ptr, doesn't move us >> > closer to defining clear ownership. >> > >> > So the floor is open. Do we: >> > >> > 1) embrace std::unique_ptr, eliminate raw pointers except in rare, >> > well-defined cases from a lifetime point of view? >> > 2) eliminate raw pointers in favour of std::shared_ptr and >> std::unique_ptr >> > then work to eliminate the std::shared_ptrs >> > 3) use process::Owned everywhere and then find places where we can >> > transition them to std::unique_ptr >> > 4) do nothing; there's no great benefit to std::unique_ptr and this >> smart >> > pointer thing is just a fad. >> > >> > >> > - dominic >> > >> > >> > -- >> > Dominic Hamon | @mrdo | Twitter >> > *There are no bad ideas; only good ideas that go horribly wrong.* >> > >> > > > -- > Dominic Hamon | @mrdo | Twitter > *There are no bad ideas; only good ideas that go horribly wrong.* > -- Dominic Hamon | @mrdo | Twitter *There are no bad ideas; only good ideas that go horribly wrong.*
