JnBrymn
JnBrymn

Reputation: 25353

Confusion concerning boost::shared_ptr

My question revolves around whether or not I must expose my use of the boost::shared_ptr from my interface and whether or not I should expose raw pointers or references from my interface.

Consider the case of a Person who has an Employeer. Employeer internally maintains all of its employees in a vector< shared_ptr< Person > >. Because of this, do best practices dictate that any interface involving Person should be a shared_ptr wrapped person?

For example, are all or only some of these ok:

Person Employeer::getPresidentCopy();
Person& Employeer::getPresidentRef();
Person* Employeer::getPresidentRawPtr();
shared_ptr<Person> Employeer::getPresidentSharedPtr();

Or for example:

void Employeer::hireByCopy(Person p);
void Employeer::hireByRef(Person& p);
void Employeer::hireByRawPtr(Person* p);
void Employeer::hireBySharedPtr(shared_ptr<Person> p);

If I later want to change the implementation to use johns_very_own_shared_ptr instead of the boost variety, am I trapped in the old implementation?

On the other hand, if I expose raw pointers or references from the interface, do I risk someone deleting the memory out from under the shared_ptr? Or do I risk the shared_ptr being deleted and making my reference invalid?


See my new question for an example involving this.

Upvotes: 0

Views: 295

Answers (6)

Max Lybbert
Max Lybbert

Reputation: 20021

I work on a moderately sized project that links in several libraries. Some of those libraries have their own memory management subsystems (APR, MFC) and really annoy me. Whether their view of the world is good or bad, it's entirely different from everybody else's and requires a little more code than they otherwise would.

Additionally, those libraries make swapping out malloc or new with jemalloc or the Boehm-Demers-Weiser garbage collector much harder (on Windows it's already hard enough).

I use shared pointers a lot in my own code, but I prefer not to tell others how to manage their memory. Instead, hand out objects whenever possible (letting the library user decide when and how to allocate the memory), and when it's not possible do one of:

  1. hand out raw pointers (plus either a promise that the pointers can be deleted or a Destroy() function to call to deallocate the objects)
  2. accept a function or STL allocator-like argument so you can hook into whatever the user's using for memory management (feel free to default to new and std::allocator)
  3. have your library users hand you allocated memory buffers (like std::vectors)

Using this kind of library doesn't have to be nasty:

// situation (1) from above
std::shared_ptr<Foo> foo(Library_factory::get_Foo(), Library_factory::deallocate);

// situation (2) above (declaration on next line is in a header file)
template<typename allocator=std::allocator<Foo> > Foo* library_function_call();
boost::shared_ptr<Foo> foo = library_function_call();

// situation (3) above, need to fill a buffer of ten objects
std::vector<Foo> foo_buffer(10);
fill_buffer(&foo_buffer[0], foo_buffer.size());

Upvotes: 0

Karl Knechtel
Karl Knechtel

Reputation: 61479

For example, are all or only some of these ok:

It depends on what you're trying to accomplish. Why does the vector hold shared_ptrs instead of just directly storing Person s by value? (And have you considered boost::ptr_vector?)

You should also consider that maybe what you really ought to hand out is a weak_ptr.

If I later want to change the implementation to use johns_very_own_shared_ptr instead of the boost variety, am I trapped in the old implementation?

Pretty much, but it's not impossible to fix. (I suspect that in C++0x, liberal use of the auto keyword will make this easier to deal with, since you won't have to modify the calling code as much, even if it didn't use typedef s.) But then, why would you ever want to do that?

On the other hand, if I expose raw pointers or references from the interface, do I risk someone deleting the memory out from under the shared_ptr?

Yes, but that's not your problem. People can extract a raw pointer from a shared_ptr and delete it, too. But if you want to avoid making things needlessly unsafe, don't return raw pointers here. References are much better because nobody ever figures they're supposed to delete &reference_received_from_api;. (I hope so, anyway ^^;;;; )

Upvotes: 3

Steve Townsend
Steve Townsend

Reputation: 54128

The only reason to hand out a shared_ptr here is if the lifetime of the returned object reference is not tied directly to the lifetime of its residence in the vector.

If you want somebody to be able to access the Person after they stop being an Employee, then shared_ptr would be appropriate. Say if you are moving the Person to the vector for a different Employer.

Upvotes: 1

Daniel Lidstr&#246;m
Daniel Lidstr&#246;m

Reputation: 10260

I would introduce a typedef and insulate myself against changes. Something like this:

typedef std::shared_ptr<Person> PersonPtr;

PersonPtr Employeer::getPresident() const;

I place typedefs like this one in a (just one) header together with forward declarations. This makes it easy to change if I would ever want to.

Upvotes: 3

Pawel Zubrycki
Pawel Zubrycki

Reputation: 2713

I shouldn't give user raw pointers, when you use shared_ptrs. User could delete it, what will cause double deletion.

To hide usage of boost:shared_ptr you can use typedef to hide actual type, and use this new type instead.

typedef boost::shared_ptr<Person> Person_sptr;

Upvotes: 2

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145204

You don't have to hand out shared_ptr, but if you hand out raw pointers you run the risk of some raw pointer persisting after the object has been destroyed.

With fatal consequences.

So, handing out references generally OK (if client code takes address then that's no more your fault than if client code takes address of *shared_ptr), but raw pointers, think first.

Cheers & hth.,

Upvotes: 2

Related Questions