I wrote this article and got some comments on it that confused me.
It basically boils down to my having seen T2
used only as a template parameter and mi
According to Herb Sutter in GOTW #100, unique_ptr
suffers from the same problem as auto_ptr
with respect to incomplete types.
...although both unique_ptr and shared_ptr can be instantiated with an incomplete type, unique_ptr’s destructor requires a complete type in order to invoke delete...
His suggestion is to declare the destructor of your containing class (i.e. T1
) in the header file, then place it's definition in a translation unit in which T2
is a complete type.
// T1.h
struct T2;
struct T1
{
~T1();
std::unique_ptr< T2 >;
};
// T1.cpp
#include "T2.h"
T1::~T1()
{
}
The following example is an attempt to demonstrate the difference between std::auto_ptr<T>
and std::unique_ptr<T>
. First consider this program consisting of 2 source files and 1 header:
The header:
// test.h
#ifndef TEST_H
#define TEST_H
#include <memory>
template <class T>
using smart_ptr = std::auto_ptr<T>;
struct T2;
struct T1
{
smart_ptr<T2> obj;
T1(T2* p);
};
T2*
source();
#endif // TEST_H
First source:
// test.cpp
#include "test.h"
int main()
{
T1 t1(source());
}
Second source:
// test2.cpp
#include "test.h"
#include <iostream>
struct T2
{
~T2() {std::cout << "~T2()\n";}
};
T1::T1(T2* p)
: obj(p)
{
}
T2*
source()
{
return new T2;
}
This program should compile (it may compile with a warning, but it should compile). But at run time it demonstrates undefined behavior. And it probably won't output:
~T2()
which indicates that T2
's destructor has not been run. At least it doesn't on my system.
If I change test.h to:
template <class T>
using smart_ptr = std::unique_ptr<T>;
Then the compiler is required to output a diagnostic (an error).
That is, when you make this mistake with auto_ptr
you get a run time error. When you make this mistake with unique_ptr
you get a compile time error. And that is the difference between auto_ptr
and unique_ptr
.
To fix the compile time error you must outline ~T1()
after T2
is complete. In test2.cpp add after T2
:
T1::~T1() = default;
Now it should compile and output:
~T2()
You will likely want to declare and outline move members as well:
T1::T1(T1&&) = default;
T1& T1::operator=(T1&&) = default;
You could make these same fixes with auto_ptr
and it would again be correct. But again, the difference between auto_ptr
and unique_ptr
is that with the former, you don't find out until run time that you have some debugging to do (modulo optional warnings your compiler may give). With the latter you are guaranteed to find out at compile time.