9

General Question: Without going into whether or not it's a good idea, how can I add an implicit conversion operator to a class that has already been defined? For example, let's say that I want unique_ptr<T> to implicitly convert to T*, but I can't just add a member conversion operator because I can't change the definition of the unique_ptr class.

Options:

  1. Is there some c++ voodoo that I can use to make this happen without creating a member function?
    Answer-So-Far: NO.
    There is no way to add an implicit conversion away from a type that you can't modify in code.
    Just ... sadness.

  2. Could I derive from std::unique_ptr and add my own member conversion function? Are there any serious downsides to this?
    Answer-So-Far: Yes (from vsoftco)
    Downsides are yet to be determined. So far inheriting from std::unique_ptr, inheriting its constructors, and declaring an implicit conversion operator has worked splendidly with hardly any code needing to be written.

  3. Am I just going to have to live without this the rest of my life?
    Answer-So-Far: We'll see...
    If I can get option 2 up and running without any serious side-effect or burdens, I'll test it out for a while and report back on whether I think it's worth it. We'll see!

Example code:

#include <algorithm>
#include <memory>
#include <vector>

struct MyClass
{
    MyClass(int v) : value(v) {}

    int value;
};

int main()
{
    auto vec = std::vector<std::unique_ptr<MyClass>>();

    vec.push_back(std::make_unique<MyClass>(1));
    vec.push_back(std::make_unique<MyClass>(2));

    // error C2664: 'void (__vectorcall *)(MyClass *)' : cannot convert argument 1 from 'std::unique_ptr<MyClass,std::default_delete<_Ty>>' to 'MyClass *'
    std::for_each(std::begin(vec), std::end(vec), [](MyClass* myClass)
    {
        myClass->value += 3;
    });
}
  • Do you really need this conversion? You can access `unique_ptr` object attributes just like a normal pointer. Also, the whole point of a `unique_ptr` is to eliminate ownership/leak issues. Every time you access the underlying pointer (using `->get()`), you add another possibility of ownership issues. – kirbyfan64sos May 31 '15 at 02:07
  • 3
    @kirbyfan64sos Not necessarily, using `->get()` is the same as taking a non-owning reference (`T*` is to `unique_ptr` what `weak_ptr` is to `shared_ptr`). Those are common, and nothing wrong with them if they're appropriate. – orlp May 31 '15 at 02:08
  • @orlp Yeah, but implicit conversions make it more likely. – kirbyfan64sos May 31 '15 at 02:09
  • @kirbyfan64sos Ah, right, I do not agree with the __implicit__ conversion either. – orlp May 31 '15 at 02:09
  • 1
    Converting `unique_ptr` to raw `T` pointer is a bad idea :( `unique_ptr` designed to own and manage resource, and most likely will destroy it when it's destructor will be called. You can miss it and try to use your conversed pointer, and ooups... Just work directly with `unique_ptr`. – vard May 31 '15 at 02:10
  • @orlp it is not the same that we have with weak_ptr and shared_ptr, shared_ptr has something like weak_reference_counter within it. You haven't such mechanism for `unique_ptr<>::get()`. It would be crazy. You could get that raw pointer, than copy it 1000 times, how could then `unique_ptr` know should resource be deleted or not. – vard May 31 '15 at 02:14
  • 1
    @vard [It __is__ the same.](http://stackoverflow.com/questions/17536731/shared-ptr-is-to-weak-ptr-as-unique-ptr-is-to-what) – orlp May 31 '15 at 02:18
  • @orlp ok, it is similar, you always could check if your raw pointer is not null, before using it. Anyway it is a bad practice. You could almost always be satisfied with smart pointers. – vard May 31 '15 at 02:23
  • 3
    @vard No, it is __not__ bad practice! Stop spreading FUD. A non-owning reference, when appropriate, is a perfectly fine solution. – orlp May 31 '15 at 02:25
  • Have you considered `for (auto&& v : vec) v->value += 3;` ? I know this doesn't answer your exact question, but by using more common idioms you might be able to remove the need for this operator – M.M May 31 '15 at 02:37
  • @orlp I completely agree with you. Though I'm trying hard to avoid the question of whether or not this *should* be done. There are legitimate reasons to use raw pointers to objects that are managed by someone else. The user of these objects really doesn't need to know the lifetime management of the objects. I shouldn't have to rewrite all my algorithms to make them aware that something is actually a unique_ptr and not just a dumb pointer. – Isaiah Hines May 31 '15 at 03:34

1 Answers1

7

If you don't want to use std::unique_ptr<>::get() function, you can:

  1. Define a free function that takes a std::unique_ptr and returns the raw pointer returned by get, although I don't think it really makes your code better, like:

    // free function
    template<typename T>
    T* get_raw_ptr(const std::unique_ptr<T>& up)
    {
        return up.get();
    }
    

    Conversions of unique_ptr to raw pointers are OK, but they have to be explicit. Implicit conversion may lead to lots of headaches, since they may happen when you least expect them.

  2. It is a bad idea to derived from std::unique_ptr, as the latter is not made to be used as a base class (doesn't have a virtual destructor). In general, it is bad to derive from Standard Library classes. However, if you really insist, you can use a wrapper in which you define the implicit conversion operator, like:

    // wrapper
    template <class T, class Deleter = std::default_delete<T>> 
    class unique_ptr_wrapper: public std::unique_ptr<T, Deleter>
    {
    public:
        using std::unique_ptr<T, Deleter>::unique_ptr; // inheriting base ctors
        operator T* () const {return this->get();}
    };
    

and use is simply like

    // wrapper usage:
    unique_ptr_wrapper<int> upw{new int{42}};
    int* p = upw; // implicit conversion OK
  1. 1 and 2 can help you, so you may improve your life ;)
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • **I'm asking how to create an implicit conversion.** I *know* that implicit conversions *may* lead to headaches, but that's what I want. I find it somewhat silly that c++ does not provide any way to specify implicit conversions without making it a class member function. – Isaiah Hines May 31 '15 at 03:19
  • 5
    As for deriving from std::unique_ptr - There is no problem with the unique_ptr not having a virtual destructor, as the derived type wouldn't have any member data to clean up anyway. Besides, we would only even need to think about this if we were passing the derived type arround using a pointer to the base type - which would probably negate the implicit type conversion that we're trying to create. – Isaiah Hines May 31 '15 at 03:20
  • @IsaiahHines well, you cannot, unless wrapping the `unique_ptr` into your own structure and defining an implicit conversion operator. But, isn't this more complicated than just using `std::unique_ptr<>::get`? Not having a derived destructor means that you won't be able to address derived classes via a `std_unique_ptr` pointer, but probably that's not an issue since most of the time one won't do such thing. In conclusion, I think the only option is to use a wrapper. – vsoftco May 31 '15 at 03:22
  • I think the destructor stuff is a non-issue when it comes to derivation. The base class (std::unique_ptr) doesn't have any virtual functions, the derived class (my_unique_ptr) wouldn't have any virtual functions and wouldn't have any extra data - so destruction of the object would always be correct regardless of how it's used. I'm thinking a wrapper is the only choice as well, given the current state of c++. **1** I wonder how complicated the wrapper might be. **2** I wonder if using the wrapper type would be burdensome. – Isaiah Hines May 31 '15 at 03:43
  • @IsaiahHines The wrapper is fairly simple, see my edit. The issue with the dtor is the following: `std::unique_ptr* up = new unique_ptr_wrapper{new int{42}}; delete up; // boom, undefined behaviour`. Of course, this example is quite artificial, as one doesn't really (or shouldn't) use pointers to `unique_ptr` in code, cannot think of a sane scenario of doing this. – vsoftco May 31 '15 at 03:46
  • The wrapper method works splendidly (so far). One correction: remove typename from the "using" line. I'll try to exhaust it to find out if there are any weaknesses. Interestingly, it looks like make_unique still works for things like vector>::push_back(make_unique()); Which, honestly, seems like a huge plus! – Isaiah Hines May 31 '15 at 04:11
  • @IsaiahHines funny, g++ compiles it with `typename`. It is after all a dependent name, but there may be some quirk in the standard that doesn't require `typename`. About the `make_unique`, I don't really know why it works... Why should `std::unique_ptr` downcast to `unique_ptr_wrapper`? Do you know? Ohh I know, because there is an inherited `unique_ptr` ctor that takes another `unique_ptr` rvalue :) So everything seem to be working then. – vsoftco May 31 '15 at 04:16
  • Visual Studios (as of **May 15, 2015** V19.00.22914.0(x86)) complains about the typename. I'm not sure what the standard has to say about it, but it doesn't seem like a dependant type to me. I tested out your "undefined behavior" scenario as well - it works fine in VS, and it seems to me like it *should* be legitimate since the derived class is just a wrapper, but I don't know the standard well enough to really say. – Isaiah Hines May 31 '15 at 04:29
  • @IsaiahHines no, deleting a derived object via a pointer to base is UB if the base doesn't have a virtual destructor, see 5.3.5/3 or the answer [here](http://stackoverflow.com/questions/8599225/virtual-destructor-and-undefined-behavior). I am 100% sure about this. In practice, most of the time the base dtor is invoked, so in this case you won't observe any weird thing. But, technically, it is undefined behaviour. Note that the dtor is considered as any other function, so if you don't mark it virtual, the base cannot access the derived dtor in its vtable. – vsoftco May 31 '15 at 04:33
  • In an inheriting constructor declaration, you are naming the constructor, not the type, so there should be no `typename`. – T.C. May 31 '15 at 04:51
  • @T.C. thanks, g++ should say something, or a diagnostic is not required? – vsoftco May 31 '15 at 04:54
  • @vsoftco Should be required, at least if you instantiate the template. – T.C. May 31 '15 at 04:55
  • @T.C. I asked the question [here](http://stackoverflow.com/questions/30554119/is-typename-required-or-not-here). – vsoftco May 31 '15 at 05:29
  • Add: `operator void* () const {return this->get();}` and then you avoid the *boom `delete` problem*, as delete will fail due to not knowing which conversion to take. – Sam Liddicott Nov 02 '15 at 10:50