Passing boost::shared_ptr by reference?

Started by
9 comments, last by Polymorphic OOP 18 years, 1 month ago
I'm new to using smart pointers, but have come to the conclusion that they will help me a great deal when managing resources in a game engine. I did a sizeof(boost::shared_ptr<whatever>) and it returned 8, which was what I excpected; the wrapped pointer itself (4 bytes) and another pointer to a Reference Count object (yet another 4 bytes). This means 4 extra bytes pushed onto the stack, when sending a smart pointer to a method by value. Is it worth passing the shared_ptr by reference instead, or might I aswell use boost::weak_ptr for something that need not to go into STL containers or be copied otherwise? This is my basic design (I've stripped out a lot of stuff in favor of focus):

class Renderer
{
public:
   void UpdateScreen(...);  // parameter lists left out
   void DrawImage(...); 
   void DrawPixelBuffer(...);
}

class GameEngine
{
public:
   GameEngine::GameEngine(...)
   {
       m_renderer = boost::shared_ptr<Renderer>(new Renderer(...));
   }

   Render();
   Update();

   typedef std::list<boost::shared_ptr<Drawable>> DrawableList;
private:
   boost::shared_ptr<Renderer> m_renderer;
   DrawableList m_drawables;
}

class Sprite : public Drawable
{
public:
   void Draw(Renderer&); // implement Drawable
}




When I first wrote the Sprite class, I had no intention on using boost::shared_ptr<Renderer> as an argument to the Draw method. Now, that I already got the Sprite class worked out, would it be bad practice to dereference m_renderer so you can pass it by reference to the Draw method of Sprite, instead of passing the smart pointer itself... This is the render method of the GameEngine, where all drawables (those implementing the Drawable interface - Sprite in this case) are drawn:

void GameEngine::Render()
{
	for (DrawableList::iterator it = drawables.begin(); 
		it != drawables.end(); it++)
	{
		(*it)->Draw(*m_renderer);
	}
	m_renderer->UpdateScreen();
}




This works and all... Basically, I'm wondering how you guys are using smart pointers? All the way, meaning make every method wanting a pointer, demand excplicitly it being a smart pointer... Or just create the smart pointer, and then send its "pointee" wherever it pleases you aslong as you know that the stack created wrapper will delete the pointee when it's time.
Advertisement
I pass objects:
  • by reference if I want to modify them
  • by value if I want to work on a copy
  • by const reference in all other cases

    This rule of thumb works for smart pointers as well and solves the stack usage problem.
  • Hi AndreasC -

    You probably should never pass a smart pointer by value. I almost always pass a const reference, and less often a non-const reference if I want to change the value. Really it's not the extra stack space that's the big issue but if your smart pointers are thread-safe it has to execute all that code to increment the reference count when you pass the smart pointer, which equals slow code. :-)
    The stack is fast, it's real, blazingly, mindbogglingly fast. Thanks to its natural cache coherency the current working area of the stack will almost always be in the L1 cache. What's more, the extra 4 bytes on the stack is a single 1 cycle instruction that will literally make 0 difference to speed (the compiler can reorder instructions to get the PUSH for free).

    Basically, I use a smart pointer in the same way I would a normal pointer (only for dynamicly allocated memory of course). Would I pass a reference to a pointer around? Only if I wanted to modify the original pointer. Would I pass a const reference to a pointer around? No, there's no point, the only thing it achieves is to add an extra dereference (which will certainly make more difference to your speed than an extra PUSH instruction).

    Quote:Original post by AndreasC
    or might I aswell use boost::weak_ptr for something that need not to go into STL containers or be copied otherwise

    I think you misunderstand the purpose of boost::weak_ptr. It's purpose is to safely keep track of a dynamically allocated object without preventing it from being destroyed. It's often used as 'handles' (think a resource manager that could unload a resource you're still holding a handle to because it hasn't been used for a while) or to resolve circular references that would prevent the reference count from reaching 0.

    BTW, it's perfectly safe to use boost::weak_ptr inside containers.

    Quote:Original post by AndreasC
    Or just create the smart pointer, and then send its "pointee" wherever it pleases you aslong as you know that the stack created wrapper will delete the pointee when it's time.


    This is a *very* bad idea. What if somewhere down the track you think, 'hey, it'd be better if I held onto that pointer for later'? You're app would come crashing down because you tried to access memory that's already been deleted. This could be even more disastrous (and difficult to find) if that memory location just happens to have been allocated to another object and your other object mysteriously gets clobbered for no apparent reason.
    "Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only verdict is vengeance; a vendetta held as a votive, not in vain, for the value and veracity of such shall one day vindicate the vigilant and the virtuous. Verily, this vichyssoise of verbiage veers most verbose, so let me simply add that it's my very good honor to meet you and you may call me V.".....V
    Quote:Original post by krum
    You probably should never pass a smart pointer by value. I almost always pass a const reference, and less often a non-const reference if I want to change the value. Really it's not the extra stack space that's the big issue but if your smart pointers are thread-safe it has to execute all that code to increment the reference count when you pass the smart pointer, which equals slow code. :-)


    While that's true, IIRC boost handles the reference counts without OS locks (but is still thread safe) so it's not all that expensive. If you're dealing with a single-threaded application you can disable the thread-safety for improved performance, but if that's a bottleneck in your application you're doing something very wrong.

    Furthermore, if you're worried about thread-safety that's a reason to never pass a smart pointer by reference. Boost smart pointers are thread-safe ONLY when you're using 2 different pointers that point at the same location. Using the same pointer (as could happen if passing references around) is NOT thread safe.
    "Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only verdict is vengeance; a vendetta held as a votive, not in vain, for the value and veracity of such shall one day vindicate the vigilant and the virtuous. Verily, this vichyssoise of verbiage veers most verbose, so let me simply add that it's my very good honor to meet you and you may call me V.".....V
    I pass boost pointer objects by value, you can think of a boost pointer as a real pointer and you tend to pass real pointers by value (copying the pointer memory to another pointer but not copying what the pointer points to)

    I've yet to see ANY speed issues surrounding this.

    if i didint make myself clear above here is a basic code sample of how i do things.


    void function(shared_ptr<ObjA> obj){    if(obj!=0)obj->doSomthing();}shared_ptr<ObjA> obj(new ObjA());function(obj);

    Raymond Jacobs, Owner - Ethereal Darkness Interactive
    www.EDIGames.com - EDIGamesCompany - @EDIGames

    I pass them by reference, for no other reason than it being a personal habbit with objects. It reduces the minor mental schizm between C++ and languages which have only pass-by-reference, as their default argument passing method.
    Generally I'd recommend passing by raw pointer if you can, as long as your function has no business concerning the lifetime of the object.

    If for some reason your function needs the interface of the shared pointer, such as to store a copy locally inside of your object, then generally you should pass the shared pointer by reference. If you don't need a copy, you don't need a copy. Period. Passing by value as opposed to reference to certain types for [potential] efficiency is the special case, not the other way around, since in general you only logically want to just pass the object and not a copy of the object. In other words, don't look at it as saying "oh, it's not that inefficient to pass by value, so I'll pass by value," as that's just silly since here you're not gaining anything logically by passing by value -- you're just needlessly copying the object for a call that doesn't need a copy of the object.

    Quote:Original post by joanusdmentia
    Quote:Original post by AndreasC
    Or just create the smart pointer, and then send its "pointee" wherever it pleases you aslong as you know that the stack created wrapper will delete the pointee when it's time.


    This is a *very* bad idea. What if somewhere down the track you think, 'hey, it'd be better if I held onto that pointer for later'? You're app would come crashing down because you tried to access memory that's already been deleted. This could be even more disastrous (and difficult to find) if that memory location just happens to have been allocated to another object and your other object mysteriously gets clobbered for no apparent reason.

    I disagree. It's not a bad idea at all pending context. In fact, you do the equivalent all the time whenever you pass a stack-allocated object around by pointer or reference, whether it just be to a single function call or to be held by another object. If you know that your code does not own the allocated memory (shared or otherwise), and you know that the object is required to be alive for the lifetime of your code which contains the pointer, there is little reason to hold a shared pointer or weak pointer to the data as opposed to a raw pointer, short of possible debugging. Not only that, but I'd even go so far as to say that holding a raw pointer in those cases is better, since holding a shared pointer only succeeds at needlessly exposing that segment of code to the implementation details regarding how the lifetime of the target object is managed, regardless of the fact that it has no business with the object's lifetime at all. All you're doing is exposing details, increasing dependencies, and making the code not work with objects allocated by other means.

    Unless you plan on always holding a weak pointer and checking if it's alive every time you use it, for debugging purposes, I'd recommend using a raw pointer whenever you can in code segments which don't deal directly with the lifetime off the referenced object.
    Thanks. A lot of useful input.

    Some reflections of mine.

    I can see most of what you guys are saying, it basically boils down to whether or not you see a smart pointer as a regular pointer, and therefore treat it as one (not much overhead as stated above - 4 bytes, come on... _that_ by itself is no reason not to pass by value. You don't go around passing doubles by reference when not needed), or you simply see it as any other class instance, which is passed by reference (const or not) by good practice.


    So...

    Don't send references to your smarties, unless you want to change it - whatever that means in this case... I mean, you can do the same thing with a passed-by-value smart pointer as you can with a passed-by-reference one. The pointee will be the same, and thus, any changes to your smart pointer inside your method, will affect the pointee regardless. Only difference is that by passing it by value is that you "wrap it in another shell" (copy the shell, or something like that) and the ref count object will be incremented...


    I can see why you'd want to send a pointer to a smart pointer by reference, if you for some reason would ever want a dynamically created smart pointer. Else, it's easier to think of smart pointers as regular ones and be done with it.


    TakeASmartPointer(shared_ptr<Whatever>*& ref_ptr_smartpointer_thingy) // eew
    {
    }

    Or...

    Let all methods demand regular pointers instead, and just extract the pointee from the smartie. I'm leaning towards this actually. Quite clean. I'd like to see smart pointers as regular ones though.

    I'm going back and forth :)
    i submit as evidence of a potentially 'proper' way of passing boost smart pointers.

    taken from the boost documentation page in the best practices section.

    http://boost.org/libs/smart_ptr/shared_ptr.htm#bestpractices

    void f(shared_ptr<int>, int);int g();void ok(){    shared_ptr<int> p(new int(2));    f(p, g());}

    Raymond Jacobs, Owner - Ethereal Darkness Interactive
    www.EDIGames.com - EDIGamesCompany - @EDIGames

    This topic is closed to new replies.

    Advertisement