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 mistakenly jumped to the conclusion that I could therefore take the opportunity of forward declaration:
struct T2;
struct T1
{
std::auto_ptr<T2> obj;
};
This invokes UB if I don't go on to define T2
somewhere in the same TU, because std::auto_ptr<T2>
calls delete
on its internal T2*
, and calling delete
on an pointer to an object of an incomplete type whose complete type has a non-trivial destructor is undefined:
[C++11: 5.3.5/5]:
If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
The GCC toolchain I happened to be using — v4.3.3 (Sourcery G++ Lite 2009q1-203) — was kind enough to let me know with a note:
note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.
though it seems difficult to get this diagnostic in other GCC versions.
My gripe was that it would be a lot easier to spot a bug like this if delete
ing a pointer to an instance of an incomplete type were ill-formed rather than UB, but that seems like an intractible problem for an implementation to solve, so I understand why it's UB.
But then I'm told that, if I were to use std::unique_ptr<T2>
instead, this would be safe and compliant.
n3035 allegedly says at 20.9.10.2:
The template parameter
T
ofunique_ptr
may be an incomplete type.
All I can find in C++11 proper is:
[C++11: 20.7.1.1.1]:
/1 The class template
default_delete
serves as the default deleter (destruction policy) for the class templateunique_ptr
./2 The template parameter
T
ofdefault_delete
may be an incomplete type.
But, default_delete
's operator()
does require a complete type:
[C++11: 20.7.1.1.2/4]:
IfT
is an incomplete type, the program is ill-formed.
I suppose my question is this:
Are the commenters on my article correct in saying that a translation unit consisting of only the following code is well-formed and well-defined? Or are they wrong?
struct T2;
struct T1
{
std::unique_ptr<T2> obj;
};
If they are correct, how is a compiler expected to implement this, given that there are good reasons for it being UB, at least when an std::auto_ptr
is used?
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.
来源:https://stackoverflow.com/questions/12764952/is-it-true-that-a-unique-ptr-declaration-unlike-a-auto-ptr-declaration-is-well