Like Tree2Likes

std::shared_ptr<T> implicit conversion from T*

This is a discussion on std::shared_ptr<T> implicit conversion from T* within the C++ Programming forums, part of the General Programming Boards category; can anyone explain to me the rationale for why the constructor of std::shared_ptr that takes a raw pointer as its ...

  1. #1
    Registered User
    Join Date
    Oct 2006
    Posts
    2,351

    std::shared_ptr<T> implicit conversion from T*

    can anyone explain to me the rationale for why the constructor of std::shared_ptr that takes a raw pointer as its parameter is marked explicit? it seems much more logical to allow implicit conversion like so:
    Code:
    std::shared_ptr<T> myPtr = new T();
    additionally, there is no assignment operator that accepts a raw pointer.

    I don't understand the reasons behind this, as it makes the code somewhat harder to follow, and less natural and convenient to write.
    Code:
    namespace life
    {
        const bool change = true;
    }

  2. #2
    Registered User whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    7,660
    I think the reasoning behind explicit constructors is explained in Effective C++ by Scott Meyer. It has to do with preventing implicit conversions. In this case, it is not necessarily proper for you to call a function that expects a shared_ptr type, and you pass in a raw pointer type.
    Code:
    void foobar(std::shared_ptr<T>& p);
    
    T* my_p = new T;
    foobar(my_p);
    Having the std::shared_ptr class's single argument constructor marked explicit helps prevent code like this from working. If you intended to pass in your raw pointer, you have to call the constructor, using constructor syntax, first.
    Last edited by whiteflags; 05-20-2013 at 02:26 PM.

  3. #3
    Registered User
    Join Date
    Oct 2006
    Posts
    2,351
    that code fails because the compiler will complain about initializing a non const reference with an rvalue, but I get where you're going with that. I guess it makes some sense, but it slows me down when I have to use constructor syntax, because it's just less natural. I suspect that others may share my frustration. maybe I'll attend the standards committee meeting in september and bring it up
    Code:
    namespace life
    {
        const bool change = true;
    }

  4. #4
    Registered User whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    7,660
    The errors generated by the compiler are:

    Code:
    C:\Users\Josh2\Documents\sandbox\main.cpp||In function 'int main()':|
    C:\Users\Josh2\Documents\sandbox\main.cpp|10|error: invalid initialization of reference of type 'std::shared_ptr<T>&' from expression of type 'T*'|
    C:\Users\Josh2\Documents\sandbox\main.cpp|5|error: in passing argument 1 of 'void foobar(std::shared_ptr<T>&)'|
    ||=== Build finished: 2 errors, 0 warnings (0 minutes, 0 seconds) ===|
    However, std::shared_ptr class can construct itself from a raw pointer. You just have to use the constructor. Like this:
    Code:
    #include <memory>
    
    struct T {};
    
    void foobar(std::shared_ptr<T>& p);
    
    int main()
    {
        T* my_p = new T;
        std::shared_ptr<T> real_p(my_p);
        foobar(real_p);
    }
    This compiles fine for me.

  5. #5
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,215
    O_o

    @Elkvis: One reason is the way ambiguity travels uphill with overloads (Example #1). This is just the simplest example I could think of out of hand. The committee isn't likely to consider a change because the same problems come around with nested functions as well.

    @whiteflags: There are two things at play (Example #2): constant "lvalue" temporaries and implicit versus explicit constructors. I feel that you are getting the two confused.

    Soma

    Code:
    template
    <
        typename FType
    >
    struct STest
    {
        template
        <
            typename RType
        >
        STest
        (
            const RType * f
        )
        {
        }
    };
    
    struct OBase1
    {
    };
    
    struct OBase2
    {
    };
    
    void Go(const STest<OBase1> & f)
    {
    }
    
    void Go(const STest<OBase2> & f)
    {
    }
    
    int main()
    {
        Go(new OBase1()); // Ambiguous!
        return(0);
    }
    Code:
    struct SImplicit
    {
        SImplicit
        (
            int f
        )
        {
        }
    };
    
    void Go1(const SImplicit & f)
    {
    }
    
    void Go2(SImplicit & f)
    {
    }
    
    struct SExplicit
    {
        explicit SExplicit
        (
            int f
        )
        {
        }
    };
    
    void Go3(const SExplicit & f)
    {
    }
    
    void Go4(SExplicit & f)
    {
    }
    
    int main()
    {
        SImplicit s1(0);
        Go1(1);
        Go1(s1);
        //Go2(1); // Invalid because the temporary which would be created is a constant "lvalue".
        Go2(s1);
        SExplicit s2(0);
        //Go3(1); // Invalid because the construct is explicit.
        Go3(SExplicit(1));
        Go3(s2);
        //Go4(1); // Invalid because the construct is explicit.
        //Go4(SExplicit(1)); // Invalid because the temporary which would be created is a constant "lvalue".
        Go4(s2);
        return(0);
    }

  6. #6
    Registered User
    Join Date
    Oct 2006
    Posts
    2,351
    you speak of ambiguity, but I guess I don't understand what's ambiguous about this:

    Code:
    std::shared_ptr<std::string> stringPtr = new std::string("foo");
    obviously this won't compile, because the constructor is marked explicit, but that's what doesn't make sense to me. it seems perfectly logical for it to work this way, and it makes code flow a little more naturally. can you think of a situation where this sort of thing would be inappropriate and/or dangerous with smart pointers?
    Code:
    namespace life
    {
        const bool change = true;
    }

  7. #7
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,215
    O_o

    Do you think I made those examples for practice or something?

    There is nothing ambiguous about that specific line, but as I explained, ambiguity travels where a template implicit constructor goes.

    Soma

  8. #8
    Registered User
    Join Date
    Oct 2006
    Posts
    2,351
    Quote Originally Posted by phantomotap View Post
    Do you think I made those examples for practice or something?
    of course not, but the examples did little to help me understand why they should answer my question.

    Quote Originally Posted by phantomotap View Post
    ambiguity travels where a template implicit constructor goes.
    that's the part I don't understand. you'll probably have to explain it in simpler terms. how does ambiguity travel? a shared_ptr has one template argument, and I guess I fail to see why implicitly constructing a shared_ptr from a raw pointer is any different from implicitly constructing a std::string from a const char pointer. if the template parameter is a well defined type, there doesn't seem to be any room for ambiguity.
    Code:
    namespace life
    {
        const bool change = true;
    }

  9. #9
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,215
    you'll probably have to explain it in simpler terms.
    O_o

    An implicit constructor is considered as part of overload resolution when the type considered is used an argument.

    Play with the code because I don't think I can explain it in simpler terms.

    a shared_ptr has one template argument, and I guess I fail to see why implicitly constructing a shared_ptr from a raw pointer is any different from implicitly constructing a std::string from a const char pointer.
    The template parameter associated with the `smart_ptr' is irrelevant.

    [Edit]
    Actually, I edited the example; as you can see, `STest' itself being a template isn't really the issue.
    [/Edit]

    Once again, look at the example. The constructor is a template.

    The ambiguity I reference comes from how implicit conversions are considered during overload resolution; the template constructor allows implicit conversion from any pointer.

    if the template parameter is a well defined type, there doesn't seem to be any room for ambiguity.
    Well, a template argument is not "well-defined" by definition.

    The `std::smart_ptr' constructor is the same, except for `explicit', as my example for you.

    The resolution is ambiguous because any pointer, even ones completely unrelated to the type desired by the `std::smart_ptr', will be considered during overload resolution.

    Soma

    Code:
    struct STest1
    {
        template
        <
            typename RType
        >
        STest1
        (
            const RType * f
        )
        {
        }
    };
    
    struct STest2
    {
        template
        <
            typename RType
        >
        STest2
        (
            const RType * f
        )
        {
        }
    };
     
    
    struct OBase1
    {
    };
    
    struct OBase2
    {
    };
    
    void Go(const STest1 & f)
    {
    }
    
    void Go(const STest2 & f)
    {
    }
    
    int main()
    {
        Go(new OBase1()); // Ambiguous!
        return(0);
    }

  10. #10
    Registered User
    Join Date
    Oct 2006
    Posts
    2,351
    ok, that makes a little more sense, now. I didn't realize that the constructor in question was a template function as well. what problem does it solve for the constructor to be a template function, and accept any pointer as a parameter? derived objects implicitly convert to their base type, so it seems unlikely that it's intended to assist with polymorphic types.
    Code:
    namespace life
    {
        const bool change = true;
    }

  11. #11
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,215
    what problem does it solve for the constructor to be a template function, and accept any pointer as a parameter?
    O_o

    Awesomeness.

    No, really, the point is to provide an incredible level of functionality. There are actually a lot of benefits related to knowing the real type of the resource during construction instead of whatever base is relevant to the interface.

    I'll give you a couple of examples:

    1): The "type erasure" technique employed allows the "deleter"--the optional function called to destroy the owned resource--to be type-safe without being an actual part of the type of the `smart_ptr'. That allows me to use `FruityDelete' and you to use `DeerDelete' with the same `std::smart_ptr<Object>' type in the interfaces.

    2): It facilitates the use of "non-polymorphic base class" aspect of the "non-virtual interface pattern" strategy. The constructor creates a type-safe object that knows the real type of the resource provided so that the "deleter"--one is created if no provided--can destroy the resource honoring the real type which allows us to have a non-virtual destructor.

    [Edit]
    By the way, these benefits directly related to parametric polymorphisms.

    So, yes, it is intended to assist with polymorphic code, just not inheritance based polymorphisms.

    As a further example, let us pretend our particular flavor of "non-virtual interface pattern" requires that object do some sort of subscribing or unsubscribing from an external resource. (You may pretend a shared library is being managed by reference count.) This strategy allows us to code a non-member, non-friend--completely normal--function depending on overload resolution for the specific derived types so that we may assume a `Unsubscribe' API exists with exactly that name. The type-safe "type erasure" employed will call the correct overload during destruction because the exact type of the resource is known.
    [/Edit]

    Soma
    Last edited by phantomotap; 05-21-2013 at 08:27 AM.

  12. #12
    Registered User
    Join Date
    Jun 2005
    Posts
    6,254
    Explicit constructors are used in reusable libraries, like the C++ standard library, when there is at least one circumstance where it is a really bad idea for implicit construction to occur.

    whiteflags gave one example where an implicit conversion of a pointer to a shared_pointer is a bad idea (as, in that example, the object just allocated will be quietly deleted by the function call). There are other examples.

    Given a choice between a little inconvenience (having to initialise without using assignment syntax, because the constructor is explicit) and having a major inconvenience (a bug that is hard to find because an implicit conversion is permitted in circumstances where something bad can happen) I will choose the small inconvenience.

    In other words, I agree with the specification of shared_ptr in this case.
    Right 98% of the time, and don't care about the other 3%.

  13. #13
    Registered User
    Join Date
    Oct 2006
    Posts
    2,351
    thanks for the great explanation, soma. I kind of understand now. I never really thought of using a smart pointer to manage anything other than automatic memory management for a normal pointer to an object. that concept is well beyond the scope of anything I do in the code that I write, so it may just be more advantageous for me to write my own much more basic shared pointer class for my purposes.
    Code:
    namespace life
    {
        const bool change = true;
    }

  14. #14
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,215
    it may just be more advantageous for me to write my own much more basic shared pointer class for my purposes.
    O_o

    How?

    This isn't something that is costing you a lot in terms of performance or memory use.

    Actually, much of the functionality is born of stuff that happens at during compilation.

    Soma

  15. #15
    Registered User
    Join Date
    Oct 2006
    Posts
    2,351
    Quote Originally Posted by phantomotap View Post
    This isn't something that is costing you a lot in terms of performance or memory use.
    but it has a significant impact, to me at least, on code readability. readability is more important to me than performance or memory use, especially since 99% or more of the processing in the programs I typically write is done by a database engine (MySQL), outside of my code.
    Code:
    namespace life
    {
        const bool change = true;
    }

Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Implicit conversion between pointers?
    By nonlinearly in forum C Programming
    Replies: 13
    Last Post: 11-13-2011, 10:02 AM
  2. Implicit conversion problem
    By Elysia in forum C++ Programming
    Replies: 3
    Last Post: 03-02-2008, 12:38 PM
  3. implicit conversion not working
    By Mr_Jack in forum C++ Programming
    Replies: 4
    Last Post: 03-09-2004, 09:50 AM
  4. implicit conversion
    By cj56 in forum C++ Programming
    Replies: 2
    Last Post: 05-26-2003, 11:36 AM
  5. Error: Implicit conversion from void*
    By Unregistered in forum Windows Programming
    Replies: 5
    Last Post: 12-12-2001, 01:38 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21