shared pointer and up-casting
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 15 of 15

Thread: shared pointer and up-casting

  1. #1
    Join Date
    Feb 2007
    Posts
    102

    shared pointer and up-casting

    Hi,

    I trying to learn about the boost::shared_ptr and I have a problem.
    Let's say that I have a base class A and a derived class B.
    Using smart pointers, I would like to be as efficient as possible, meaning that I don't want in increase the counter of a variable if it is not necessary.

    Let's say that i have 1 variables:
    shared_ptr<B> b(new B);

    Now, if i write this:
    const shared_ptr<B> &c = b;
    then the b counter was not increment, perfect.

    but if I write this:
    const shared_ptr<A> &c = b;
    or const shared_ptr<A> &c = static_pointer_cast<A>(b);
    then the b counter was increased.

    This is a problem when passing b as an argument of a function from the base class A.
    I don't want to debate if I should mind or not because it probably won't cause any performance issue, but I would like to know if there is a way to cast from B to A without incrementing the counter.
    So far, the only way I found working (I am not sure if this is safe) is:

    const shared_ptr<A> &c = *(const shared_ptr<A> *)&b;

    Does someone have a clean solution to up cast an object without incrementing the reference counter?
    I am a bit lost here.

    Thank you for reading my post,

    madric

  2. #2
    Join Date
    Jan 2006
    Location
    Singapore
    Posts
    6,391

    Re: shared pointer and up-casting

    Quote Originally Posted by madric
    This is a problem when passing b as an argument of a function from the base class A.
    The B object will presumably exist throughout the lifetime of that function, and the pointer parameter does not have ownership of the object. As such, one possibility is to use an ordinary pointer parameter of type A* or const A*, then you call get() on the shared_ptr when calling the function. Or, maybe it makes even more sense to have an A& or const A& parameter instead.

    Quote Originally Posted by madric
    So far, the only way I found working (I am not sure if this is safe) is:

    const shared_ptr<A> &c = *(const shared_ptr<A> *)&b;
    That cast looks wrong: you are trying to force a pointer to a shared_ptr<B> to be a pointer to a shared_ptr<A>, but although B is derived from A, shared_ptr<B> is not derived from shared_ptr<A>. If you change that C-style cast to a static_cast, I expect a compile error.
    C + C++ Compiler: MinGW port of GCC
    Build + Version Control System: SCons + Bazaar

    Look up a C/C++ Reference and learn How To Ask Questions The Smart Way
    Kindly rate my posts if you found them useful

  3. #3
    Join Date
    Feb 2007
    Posts
    102

    Re: shared pointer and up-casting

    Thank you laserlight for your answer.

    Yes, I thought about the possibility to send the raw pointer directly. But the function may or may not asking to share the ownership as well (by assigning the smart pointer to a class member for ex). I could use then the shared_from_this but this case also, it will increase the counter two times (one for shared_from_this and one for the assignment).

    The second cast looks wrong I admit it. But I don't understand why it would not work if B derived from A and in practice, it works just fine. But it is kind of dirty...

  4. #4
    Join Date
    Jul 2005
    Location
    Netherlands
    Posts
    2,016

    Re: shared pointer and up-casting

    Quote Originally Posted by madric View Post
    Yes, I thought about the possibility to send the raw pointer directly. But the function may or may not asking to share the ownership as well (by assigning the smart pointer to a class member for ex). I could use then the shared_from_this but this case also, it will increase the counter two times (one for shared_from_this and one for the assignment).
    You don't know how many times the counter will be increased, because it depends on which optimizations the compiler will perform. If the compiler is not smart enough optimize the assignment, then you can still swap the shared_ptrs instead. But if you want to go this route, why not just pass a weak_ptr directly?
    Quote Originally Posted by madric View Post
    The second cast looks wrong I admit it. But I don't understand why it would not work if B derived from A and in practice, it works just fine. But it is kind of dirty...
    You are casting between unrelated types. It may happen to work, but you have no guarantees.
    Cheers, D Drmmr

    Please put [code][/code] tags around your code to preserve indentation and make it more readable.

    As long as man ascribes to himself what is merely a posibility, he will not work for the attainment of it. - P. D. Ouspensky

  5. #5
    Join Date
    Apr 1999
    Posts
    27,446

    Re: shared pointer and up-casting

    Quote Originally Posted by madric View Post
    The second cast looks wrong I admit it. But I don't understand why it would not work if B derived from A and in practice, it works just fine.
    As D_Drmmr mentioned, you are casting between unrelated types. Your program, even though you say is "fine", is defacto unstable. A change in compiler options could even make your code break, or a service pack/upgrade to your existing compiler could break your code. In other words, your "solution" of casting is a non-starter.

    Look at this thread here, and my responses that show up at the end of the thread, including a solution to the original posters problem:
    http://forums.codeguru.com/showthrea...st-shared_ptr)
    Does someone have a clean solution to up cast an object without incrementing the reference counter?
    Maybe your problem is a design issue instead of a casting issue. A properly designed program based on templates would be a solution to your problem without any casts whatsoever.

    What it seems like you're doing now is that you have a pattern (in this case, the pattern is a function or set of functions), but you are applying two or more different types to the same pattern. When you have that, the primary solution is to templatize the pattern, and pass the differing type as a template argument (if the compiler cannot deduce the argument).

    Regards,

    Paul McKenzie
    Last edited by Paul McKenzie; August 12th, 2013 at 01:50 PM.

  6. #6
    Join Date
    Feb 2007
    Posts
    102

    Re: shared pointer and up-casting

    Thank you for the replies. The template idea is good but still I think it cannot solve my original problem.

    What I need to do is to have like a tree structure of items. I have a base class A that will contain a list of children and the parent. Then I have 2 derived classes B and C from A. I need a tree of B and C elements, each node can have children from B and C at the same time.

    Then I would implement something like that:

    Code:
    class A {
    public:
    ...
    std::list<shared_ptr<A>> _children;
    weak_ptr<A> parent;
    void add_child(const shared_ptr<A> &item);
    ...
    };
    
    class B : public class A {...}
    class C : public class A {...}
    
    void someFunction() {
    
    shared_ptr<B> root = new shared_ptr<B>(new B);
    shared_ptr<B> child1 = new shared_ptr<B>(new B);
    shared_ptr<C> child2 = new shared_ptr<C>(new C);
    root->add_child(child1);
    root->add_child(child2);
    
    }
    
    }
    Each time the add_child function will be called, the reference counter of the parameter is increased. My understanding is that this is not necessary. If I am wrong, I don't understand why. I believe this is standard way to do, I don't think I can replace this code by something based on template since the child list can contain mixes of types. I could maybe out the add_child function and base it on a template but I would have to do it for many functions and this would add complexity in my feeling.
    You could say that I could create child1 and child2 without share_ptr and send the raw pointer in the add_child function but what if the child1 is been reattached?

    I tried also to compile in release mode, the result is the same

  7. #7
    Join Date
    Apr 1999
    Posts
    27,446

    Re: shared pointer and up-casting

    Quote Originally Posted by madric View Post
    Thank you for the replies. The template idea is good but still I think it cannot solve my original problem.
    Code:
    class A 
    {
        public:
        std::list<shared_ptr<A>> _children;
        weak_ptr<A> parent;
    
        template <typename U>
        void add_child(const shared_ptr<U> &item)
        {
           // code based on U
        }
    };
    Just that one change shows how you could have rewritten the function. Nothing stops you from having templated member functions. Now no casts are needed when calling add_child.

    I just think you haven't discovered all you can do with templates and a solid design. There have been libraries that are much more complex than what you're trying to accomplish that are entirely or mostly based on templates. The keyword being design.

    Regards,

    Paul McKenzie
    Last edited by Paul McKenzie; August 12th, 2013 at 10:47 PM.

  8. #8
    Join Date
    Feb 2007
    Posts
    102

    Re: shared pointer and up-casting

    That is very good. I guess I still have to learn about templates
    Took my lesson today, thank you very much.

  9. #9
    Join Date
    Aug 2013
    Posts
    55

    Re: shared pointer and up-casting

    Quote Originally Posted by madric View Post
    Each time the add_child function will be called, the reference counter of the parameter is increased.
    That's not true. Since the smart pointer is passed to add_child by const reference the call itself won't increment the reference counter because the smart pointer is not being copied (like it would be if it instead were passed by value). So if the reference counter is incremented it's because the smart pointer is copied after it has been passed.

    And that's most likely what happens. Add_child probably is storing the smart pointer somewhere and the copying involved will increment the reference counter. If on the other hand the smart pointer were just used in add_child without being copied then the reference counter would remain unchanged.

    The reference counter is incremented each time a smart pointer is copied. And when a copy is destroyed the reference counter is decremented. No amount of template usage or type casting or optimization by compiler will influence this basic behaviour.
    Last edited by zizz; August 13th, 2013 at 03:59 AM.

  10. #10
    Join Date
    Feb 2007
    Posts
    102

    Re: shared pointer and up-casting

    Actually, this looks true.
    Here is a piece of very simple code, you can try it in your side:

    Code:
    class B : public class A {...}
    
    void function1(const shared_ptr<A> &item) {
    
         cout<<item.use_count()<<endl;
    
    }
    
    void function2() {
    
         shared_ptr<B> b(new B);
         shared_ptr<A> a(new A);
    
         cout<<b.use_count()<<endl;
         function1(b);
    
         cout<<a.use_count()<<endl;
         function1(a);
    
    }
    
    OUTPUT RESULT:
    1
    2
    1
    1
    Paul, thank you for the idea of template function. It works but for some of my functions, I don't want to make the code public. Using a template function, I have to write it in the header right? Would you think about something else without using a template function?

    Thanks again for all your replies, it was helpful so far!
    Last edited by madric; August 13th, 2013 at 11:09 PM.

  11. #11
    Join Date
    Aug 2013
    Posts
    55

    Re: shared pointer and up-casting

    Quote Originally Posted by madric View Post
    Actually, this looks true.
    Here is a piece of very simple code, you can try it in your side:
    Okay I was wrong. The upcast in the call induces a copying operation. It seems a new smart pointer of the supertype is created and passed in the call.

    I think formally this has to do with the fact that although B is a subtype of A, shared_ptr<B> is not a subtype of shared_ptr<A>. It would mean that this isn't a typecasting situation but a conversion. Shared_ptr<B> is being converted into shared_ptr<A>, not upcasted.

    Well, I'm relying heavily on smart pointers in my applications and this is a setback indeed. So now I'm on the hunt for a solution to this problem too.

    Embarrissingly (for me) a template can solve the problem indeed (although it's not a perfect solution),
    Code:
    template <typename T>
    void function1(const boost::shared_ptr<T>& item) {
       std::cout << item.use_count() << std::endl;
    }
    On the other hand the negative performance impact application wide is much smaller than I first anticipated because most of the time A is a public interface and the B class a hidden implementation. So this is the normal situation,

    Code:
    shared_ptr<A> a(new B);
    Here the type upcast from B to A is already done at the outset so typically there will be no reference counter incrementing later when this shared_ptr is passed by const reference.

    To me this behaviour of shared_ptr came as a big surprise but fortunately the performance inpact on my code is neglectable (due to the fact that implicit upcasting in method calls is not very common as it turns out). But this is something I definately will look out for in case of performance bottlenecks.
    Last edited by zizz; August 14th, 2013 at 05:18 AM.

  12. #12
    Join Date
    Apr 1999
    Posts
    27,446

    Re: shared pointer and up-casting

    Quote Originally Posted by madric View Post
    Paul, thank you for the idea of template function. It works but for some of my functions, I don't want to make the code public.
    1) Why the secrecy of your implementation? You're supplying the user source code, so they expect to have source code.

    2) How would you have solved this issue (making the code non-public) with a non-template function?

    I am a user of your class. At some point, without some sort of sophistication, I have access to the source of the functions, regardless of whether they are inline or not.

    I still have to compile my project using your code. So the only way to prevent me (again without sophistication) from getting linker errors is for you to provide me the full source code, or object code (obj file, library file, etc.) instead of a CPP module. So in a "normal" source code setup, you will wind up with the same problem of hiding your source code, whether you're using templates or not, unless you provide these "private" parts as a set of obj or lib files.

    Look up the "Pimpl" method. However, this method is used to reduce compilation times, not stop spying.

    If your real goal in hiding your source is that you're afraid of users writing code assuming that your internal implementation is done in a certain way, then that's their fault, not yours, if the user starts to write code assuming your class does things a certain way. The user is supposed to only use the public interface, and the implementation of the public interface is supposed to be a "black box" that just does the work necessary. As long as you document the public interface, the internals to a user is not important.

    The user is not to assume how the internals are implemented, even though they can see the internals. If you happen to change the internal implementation, and for example that class variable they assumed was there is no longer there in a later version, then they're the ones that shot themselves in the foot -- you're not responsible for their poor programming decisions.

    Now, if the internals are really and truly secret, then your design has to be library or object-code based, where you provide the internals in the form of these types of files (maybe a DLL that just exposes a 'C' interface, and you write wrapper functions).

    Regards,

    Paul McKenzie
    Last edited by Paul McKenzie; August 14th, 2013 at 01:01 PM.

  13. #13
    Join Date
    Aug 2013
    Posts
    55

    Re: shared pointer and up-casting

    I think I'm on to something here. The std::move function seems to prevent the unneccessary copy from being made,

    Code:
    function1(std::move(b));
    This is ugly and tedious so the move functionality preferably should take place somewhere else hidden from view. But I'm not yet quite up to the new move semantics of C++ 11 to know exactly how to accomplish that.

    Besides it's not alltogether certain std::move is cheaper than the reference counter increment it's avoiding.
    Last edited by zizz; August 19th, 2013 at 04:28 AM.

  14. #14
    Join Date
    Aug 2013
    Posts
    55

    Re: shared pointer and up-casting

    It came as a big surprise to me that upcasting of a shared_ptr (as a const reference parameter in a function call) induced a reference counter increment.

    By experiment I've found that the std::move function removes this increment (see my previous post). I vaguely understand why but I have no idea how this "solution" could be generally implemented in a design.

    If shared_ptr is to be "like a pointer" semantically then this counter increment upon upcast shouldn't take place really. So what's going on here. Anyone has an idea?
    Last edited by zizz; August 19th, 2013 at 07:35 PM.

  15. #15
    Join Date
    Oct 2008
    Posts
    1,168

    Re: shared pointer and up-casting

    Code:
    void someFunction() {
    	shared_ptr<B> root = new shared_ptr<B>(new B);
    	...
    I suppose you meant "make_shared<B>()" there ...

    Quote Originally Posted by madric View Post
    Each time the add_child function will be called, the reference counter of the parameter is increased. My understanding is that this is not necessary.
    indeed, conceptually, if those childs passed to "root" needs not to be shared with anybody else then yes, adding the reference count is not necessary. But if this is the case then you can just pass the result of make_shared to add_child by value, the resulting shared_ptr will be move-constructed and the reference counter will remain untouched:

    Code:
    void someFunction()
    {
    	shared_ptr<B> root = make_shared<B>();
    
    	root->add_child( make_shared<B>() );
    	root->add_child( make_shared<C>() );
    }
    
    void A::add_child( shared_ptr<A> item )
    	// there's no point passing by reference as the function intent makes clear
    	// that ownership will be shared anyway ...
    {
    	// ...
    	
    	_children.push_back( move( item ) );
    
    	// ...
    }
    PS: I'm sure this will work as expected with c++11 STL, but I cannot recall if boost c++11 move emulation actually supports the above or not ...
    Last edited by superbonzo; August 20th, 2013 at 06:03 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center