7

I'm concerned about using an incomplete type with a smart pointer and how the pointer is deleted. Is the following code safe? I don't think it would be, as main.cpp would generate Farm's default destructor which wouldn't see the complete type. To make it safe, I think I should create a non-inline destructor which sees the complete type. Is that correct?

Also is it the same if I used std::vector<Cow> in Farm instead?

farm.h

class Cow;

struct Farm
{
    Farm();
    // ~Farm();
    std::unique_ptr<Cow> cow;
};

farm.cpp

#include "cow.h"
// cow now complete

Farm::Farm()
{
    cow.reset(new Cow);
}

// Farm::~Farm() {}

main.cpp

#include "farm.h"

int main()
{
    Farm farm;
}

Edit: I tried to compile with Visual Studio without the destructor and it says error C2338: can't delete an incomplete type. I guess that answers my question.

Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
  • Note that with auto_ptr, the code compiles :-/ – Jarod42 Sep 01 '13 at 16:02
  • @Jarod: Yes, and silently does the wrong thing. – Ben Voigt Sep 01 '13 at 16:04
  • 1
    http://stackoverflow.com/questions/8595471/does-the-gotw-101-solution-actually-solve-anything – Ben Voigt Sep 01 '13 at 16:07
  • @Jarod42: With `auto_ptr` the code is bad, because the default destructor of `Farm` uses the destructor of `auto_ptr`, which just does `delete` with the incomplete type `Cow`. This has UB if the actual destructor of `Cow` is non-trivial, and is valid otherwise. Since the compiler can't tell which it is (not having the definition of `Cow`), it goes ahead and outputs the code. – Steve Jessop Sep 01 '13 at 16:07

2 Answers2

4

I don't think your code should compile (and it doesn't in gcc)

std::unique_ptr<Cow> uses std::default_delete<Cow>, and std::default_delete<Cow>::operator() should fail to instantiate for an incomplete type Cow.

See also Is it true that a unique_ptr declaration, unlike a auto_ptr declaration, is well-defined when its template type is of an incomplete type?

So you're right: you need to ensure that default_delete<Cow>::operator() is instantiated somewhere that the Cow type is complete. Which means the destructor of Farm needs to be defined in such a place.

I've just noticed that your subject says "smart pointer", while the question specifies unique_ptr. The answer would be different for a shared_ptr, since std::shared_ptr<Cow>::reset() is a function template that captures the (static) type of the pointer passed to it and stores a deleter. So with shared_ptr all you need is that the call to reset has the complete type Cow -- the location of the destructor doesn't matter.

Community
  • 1
  • 1
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Thanks. But when does shared_ptr need the complete type? – Neil Kirk Sep 01 '13 at 16:07
  • @NeilKirk: at the points where the deleter is inferred: -- `reset()`, constructors and assignment operators from pointer types. If you specify the deleter in those cases then `shared_ptr` doesn't need the complete type ever, only your custom deleter does. And of course anyone who uses the result of `operator->` and `operator*` ;-) – Steve Jessop Sep 01 '13 at 16:09
  • `shared_ptr` works with incomplete type, but `unique_ptr` does not – linquize Jan 01 '15 at 08:55
1

The default destructor for Farm will include a desructor for the unique_ptr, which will include a destructor for Cow. This call will be done even if there's no definition available at the time of compiling. The linker will be expected to connect things up after the fact.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622