std::unique_ptr issues

Started by
13 comments, last by mpratscher 9 years, 7 months ago


I actually use unique_ptr with forward declarations a lot. Works great.

Ah, I see, so the definition has to be available at the point where I instanciate the class then. Thats only half-bad. Also explains why half my classes "mysteriously" worked with forward-declaration and the others didn't.


Your problem seems to be something causing std::unique_ptr to instantiate the destructor before Bar is defined. Pay close attention to the warning to see if it indicates another file in your compilation unit. This will usually happen if you have code in your header file that is trying to manipulate the unique_ptr, even if the manipulation is "invisible" (i.e. constructor, destructor, accessing, etc).

Unfortunately the warning at the end of the template-error only pointed to the member variable declaration itself, but maybe I missed something :/


Also, unique_ptr is not copyable, so anything that stores them can not be copied. This includes containers, which is why compilation failed with the auto-generated copy constructor and copy assignment operator. Since unique_ptr indicates single-ownership, this is desired, and you should delete the copy constructor and assignment operator as you did above. If you want to allow someone to move ownership, then supply your own move constructor and operator which should work just fine.

Yeah, I get that, but on the same time it appears inconsistent that having a reference or unique_ptr as member will implicitely disallow copying without any warning/errors (unless you try to copy, duh) but having it in a container will generate compile errors, even if I never try to compile the class. I also kind of disliked it because at first I thought using unique_ptr would remove a lot of unnecessary code... which it does in the cpp-files with all the deletes gone, and now that I know the cause for the undefined-type-warning I can get rid of all the empty dtors, but still I have to bloat the interface a little with all the default/delete-methods. But I quess you could argue that it was a fault for me not doing this before anyways, since being able to copy a class with dynamic memory with default copy-ctor sucks by itself.


I suspect all your problems would disappear if you used a pimpl. Hide your private parts.

That would indeed solve the issue, and speed up compile time by another factor, but I'm not quite willing to do that. I've already considered using pimpl, but I decided against in mostly due to performance concerns and additional development overhead, that currently stands in no relation to the gain. Regarding performance, I know its probably "premature optimization" in a way, but with all the talks about cache coherency & memory fregmentation, I'm certain that for most parts I'm better of without it. I just recently squished out about 0,1 ms by constructing my state machine statically instead of via new, so yeah...

Advertisement
I came across something similar recently and found adding a blank destructor to the owning class, with its body defined in the .cpp after the inclusion of the full header meant I could just forward define the type in the header and declare one of these templated pointers. As soon as I remove the destructor from the .cpp and let the compiler auto-generate it or have it stubbed as an inline empty destructor, the warning comes back.

So yes, it is to do with when the compiler generates the destruction code. The full class needs to be visible at that point, but (on GCC at least) if you have a (possibly empty) destructor in the .cpp that seems to fix it.

Not sure if this is fully defined behaviour or not though.

Really its like this:


class B;

class A
{
public:
    ~A(){ delete b; }

    B *b;
};
Clearly not going to work as b is undefined at the point of the delete.


class B;

class A
{
public:
    ~A();

    B *b;
};

// cpp
#include "B.h"

A::~A(){ delete b; }
Is clearly fine. The fact you are using template smart pointers is kind of irrelevant really.

Yeah, I get that, but on the same time it appears inconsistent that having a reference or unique_ptr as member will implicitely disallow copying without any warning/errors (unless you try to copy, duh) but having it in a container will generate compile errors, even if I never try to compile the class. I also kind of disliked it because at first I thought using unique_ptr would remove a lot of unnecessary code... which it does in the cpp-files with all the deletes gone, and now that I know the cause for the undefined-type-warning I can get rid of all the empty dtors, but still I have to bloat the interface a little with all the default/delete-methods. But I quess you could argue that it was a fault for me not doing this before anyways, since being able to copy a class with dynamic memory with default copy-ctor sucks by itself.

The default methods are also a problem.

Any code that constructs a unique_ptr<T> has to also construct the default deleter of T, which has that static_assert with the sizeof(T), which means it needs the complete type of T. Putting a default constructor in your container class causes the problem just as much as the default destructor. Your constructor, destructor, and really anything else that requires any detail of the class T should go into the .cpp file that implements your container. By putting the constructor and destructor into the .cpp file, you should be able to save compile time (they'll only be compiled in one place) and solve this problem.

This compiles (if you don't call the code, it doesn't need to exist):


class Member;
struct Test
{
    //Test();
    //~Test();
    std::unique_ptr<Member> mPtr;
};
int main()
{
    //Test t;
    return 0;
}

This is just a linker error (you can fix the problem with another .cpp file):


class Member;
struct Test
{
    Test();
    ~Test();
    std::unique_ptr<Member> mPtr;
};
int main()
{
    Test t;
    return 0;
}

And this is a compile error (compiling the default constructor needs to actually try to implement parts of the unique_ptr with an incomplete type, which opens a whole can of worms):


class Member;
struct Test
{
    //Test();
    ~Test();
    std::unique_ptr<Member> mPtr;
};
int main()
{
    Test t;
    return 0;
}

Alright, having the empty dtor AND and empty default ctor does the trick. That still makes things a little more ugly than I hoped too but well, its better than removing forward-declarations. Its not really a disadvantage, I would have had to define a ctor/dtor when using a raw pointer too, I was just hoping to get rid of them completely. Nevermind, I can work that way now, thanks to all.

This may help:

http://stackoverflow.com/questions/6012157/is-stdunique-ptrt-required-to-know-the-full-definition-of-t

This topic is closed to new replies.

Advertisement