53

I've been making some objects using the pimpl idiom, but I'm not sure whether to use std::shared_ptr or std::unique_ptr.

I understand that std::unique_ptr is more efficient, but this isn't so much of an issue for me, as these objects are relatively heavyweight anyway so the cost of std::shared_ptr over std::unique_ptr is relatively minor.

I'm currently going with std::shared_ptr just because of the extra flexibility. For example, using a std::shared_ptr allows me to store these objects in a hashmap for quick access while still being able to return copies of these objects to callers (as I believe any iterators or references may quickly become invalid).

However, these objects in a way really aren't being copied, as changes affect all copies, so I was wondering that perhaps using std::shared_ptr and allowing copies is some sort of anti-pattern or bad thing.

Is this correct?

Fund Monica's Lawsuit
  • 5,770
  • 8
  • 46
  • 65
Clinton
  • 20,364
  • 13
  • 59
  • 142
  • Using the one or the other changes deeply the copy semantics you give to your objects. There are uses for both. I'd say the more idiomatic in the C++ world is `unique_ptr`, but object with a shared implementations have their use, particularly if you are writing "foreign" code (eg. COM, C++/CLI), or if the class really looks like a "reference type". – Alexandre C. Apr 07 '11 at 08:38
  • Similar question: http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom – Rolf Kristensen Nov 21 '11 at 22:31
  • The recommended way in C++11 is to use unique_ptr, after all you have no need to copy or share the implementation with anyone. Also unique_ptr is quicker at run-time. – Damian Jun 08 '16 at 16:30

4 Answers4

37

I've been making some objects using the pimpl idiom, but I'm not sure whether to used shared_ptr or unique_ptr.

Definitely unique_ptr or scoped_ptr.

Pimpl is not a pattern, but an idiom, which deals with compile-time dependency and binary compatibility. It should not affect the semantics of the objects, especially with regard to its copying behavior.

You may use whatever kind of smart pointer you want under the hood, but those 2 guarantee that you won't accidentally share the implementation between two distinct objects, as they require a conscious decision about the implementation of the copy constructor and assignment operator.

However, these objects in a way really aren't being copied, as changes affect all copies, so I was wondering that perhaps using shared_ptr and allowing copies is some sort of anti-pattern or bad thing.

It is not an anti-pattern, in fact, it is a pattern: Aliasing. You already use it, in C++, with bare pointers and references. shared_ptr offer an extra measure of "safety" to avoid dead references, at the cost of extra complexity and new issues (beware of cycles which create memory leaks).


Unrelated to Pimpl

I understand unique_ptr is more efficient, but this isn't so much of an issue for me, as these objects are relatively heavyweight anyway so the cost of shared_ptr over unique_ptr is relatively minor.

If you can factor out some state, you may want to take a look at the Flyweight pattern.

Matthieu M.
  • 251,718
  • 39
  • 369
  • 642
  • So are you're saying that ordinarily, copy constructors should do deep copies (or something functionally equivalent, i.e. copy on write) with the exception of pointers/smart pointers? – Clinton Apr 07 '11 at 07:48
  • 3
    @Clinton: semantically, a copy is separated from its source, the implementation details are of no interest to the user :) In C++ this is not the case for pointers and references, yet the vocabulary has never really been adapted, thus the awkwardness when discussing those aspects. .. Whether in your case you prefer a deep-copy or a shallow-copy should only depend on the semantics you wish to give to your class, and the implementation will follow suite. – Matthieu M. Apr 07 '11 at 08:27
  • "_yet the vocabulary has never really been adapted_" What do you mean? – curiousguy Oct 08 '11 at 16:54
  • @curiousguy: 5 months after :x ? I think that my point was that a "copy" constructor doing a shallow copy was kinda weird, but really... I am not sure :p – Matthieu M. Oct 08 '11 at 17:29
  • In some exceptional cases, you want to use `shared_ptr`, actually. You may want an object `Foo` whose implementation can be shared among many `Foo` objects, using reference counting and copy-on-write semantics. – Frank Jun 01 '12 at 01:17
  • 1
    @Frank: COW is an option (and I did say that `shared_ptr` was adapted to some cases), but we were talking about Pimpl here; it's not one of those cases where `shared_ptr` is adapted. – Matthieu M. Jun 01 '12 at 06:14
  • What's the difference between a "pattern" and an "idiom"? – conio Jun 12 '17 at 23:37
  • @conio: The definition I apply is that a pattern is something that is useful in a broad number of languages (for example, the famous Design Patterns from the Gang of Four apply to any language in which you can use object-oriented designs) whereas an idiom is specific to a specific language (or a tiny few). PIMPL is specifically a C or C++ idiom, as it was created to trim header files. – Matthieu M. Jun 13 '17 at 06:35
  • @MatthieuM. Well, you're entitled to your opinion, but you basically claim that CRTP does not exist. Maybe there's a "CRTI". I'm not sure that's a common or reasonable opinion or definition. – conio Jun 13 '17 at 10:39
  • @conio: CRTP is not specific to C or C++, I expect it can be used in D at least and possibly in Java/C# (not sure, as it seems to require unconstrained generics). In any case, the terms certainly overlap, and other people have different opinions of their meaning, ... it's a bit of a mess :x – Matthieu M. Jun 13 '17 at 10:47
  • @MatthieuM. Same goes for pimpl. It too is not specific to C++. It too can be used in D. https://dlang.org/spec/struct.html https://stackoverflow.com/questions/20469768/pimpl-idiom-in-the-d-programming-language – conio Jun 13 '17 at 11:26
  • @conio: The very page you link to calls it the PIMPL idiom ;) In any case, just because it's usable in D on top of C or C++ does not necessarily breach my definition; it's still niche. – Matthieu M. Jun 13 '17 at 11:46
  • @MatthieuM. But that web page does not argue that "idiom" and "pattern" are mutually exclusive as you did. PIMPL is usable in D "on top of C" just as CRTP is available in D "on top of C" and in C#/Java "on top of" a generics syntax copied directly from C++. When you change you definition that's a sign you messed up. You first decided that "idiom" and "pattern" are mutually exclusive, and not trying to invent a definition that would make it work. Fine. I got it. – conio Jun 14 '17 at 01:15
12

If you use shared_ptr, it's not really the classical pimpl idiom (unless you take additional steps). But the real question is why you want to use a smart pointer to begin with; it's very clear where the delete should occur, and there's no issue of exception safety or other to be concerned with. At most, a smart pointer will save you a line or two of code. And the only one which has the correct semantics is boost::scoped_ptr, and I don't think it works in this case. (IIRC, it requires a complete type in order to be instantiated, but I could be wrong.)

An important aspect of the pimpl idiom is that its use should be transparent to the client; the class should behave exactly as if it were implemented classically. This means either inhibiting copy and assignment or implementing deep copy, unless the class is immutable (no non-const member functions). None of the usual smart pointers implement deep copy; you could implement one, of course, but it would probably still require a complete type whenever the copy occurs, which means that you'd still have to provide a user defined copy constructor and assignment operator (since they can't be inline). Given this, it's probably not worth the bother using the smart pointer.

An exception is if the objects are immutable. In this case, it doesn't matter whether the copy is deep or not, and shared_ptr handles the situation completely.

James Kanze
  • 142,482
  • 15
  • 169
  • 310
6

When you use a shared_ptr (for example in a container, then look this up and return it by-value), you are not causing a copy of the object it points to, simply a copy of the pointer with a reference count.

This means that if you modify the underlying object from multiple points, then you affect changes on the same instance. This is exactly what it is designed for, so not some anti-pattern!

When passing a shared_ptr (as the comments say,) it's better to pass by const reference and copy (there by incrementing the reference count) where needed. As for return, case-by-case.

Nim
  • 32,149
  • 2
  • 56
  • 98
  • 2
    This is good advice, but I think "always pass and return by value" is a bit strong. The preference is to pass by const reference or rvalue reference. Passing by plain, modifiable reference is even still sometimes appropriate. – Potatoswatter Apr 07 '11 at 06:47
  • @Johann: Ah, I was thinking about the effect of pass-by-value on an object with a member (pimpl) `shared_ptr` or `unique_ptr`. In this context, the smart pointer itself shouldn't be getting passed around. – Potatoswatter Apr 07 '11 at 07:27
  • 2
    @Johann Gerell: No, you're __not__ supposed to pass shared_ptr by value. You can but that's not the recommended way. The recommended way is to pass them by const reference. This is both more efficient than copying a shared_ptr and also absolutely safe, since concurrent reads of the same shared_ptr instance are ok as per the shared_ptr documentation. – antred Aug 26 '14 at 13:04
0

Yes, please use them. Simply put, the shared_ptr is an implementation of smart pointer. unique_ptr is an implementation of automatic pointer:

Bhargav Rao
  • 41,091
  • 27
  • 112
  • 129
Trombe
  • 163
  • 1
  • 8