Remarque: la réponse est en dessous de la ligne de séparation.
Avec la permission de Howard I (Alf) suis modifiant cette réponse en montrant pourquoi le code a un Comportement Indéfini, et un don de séparateurs en béton par exemple.
Depuis cela invalide la plupart de l'original de la réponse, je suis en plaçant ce texte à l'avant, pour éviter que les gens ne lire que le début de la réponse originale à cette question et d'être induit en erreur. Cependant, enterré dans l'original était-ce important d'info:
L' sizeof
unique_ptr
est le même que pour auto_ptr
et T*
si vous êtes en défaut la deleter, ou spécifier un apatride deleter. Si vous spécifiez un “stateful” deleter (comme un pointeur de fonction), puis de la mémoire supplémentaire est pile-allouée pour stocker l'état de votre deleter.
Cela dit, la question est
- comment créer un
unique_ptr<T>
personnalisé deleter, qui sera correctement détruire l'arbitre, même après l' unique_ptr<T>
a été déplacé à un unique_ptr<BaseOfT>
.
C'est une des principales raison de ma question, par exemple, il se rapporte à l'utilisation d' unique_ptr
pour le PIMPL idiome.
Notons d'abord que l'adresse d'un objet dépend du type qu'il est consulté en tant que:
#include <iostream>
using namespace std;
struct Base { int x; };
struct Derived: Base { virtual ~Derived() {} };
int main()
{
Derived* p1 = new Derived;
Base* p2 = p1;
cout << "As Derived at " << p1 << ", as Base at " << p2 << ".\n";
}
Les tests qu'avec Visual C++ 10.0 et MinGW g++ 4.4.1:
d:\dev\test\hh> cl changing_address.cpp /Fe"b"
changing_address.cpp
d:\dev\test\hh> b
Comme Dérivés à l'00721A50, comme Base à 00721A54.
d:\dev\test\hh> g++ changing_address.cpp
d:\dev\test\hh> un
Comme Dérivés à l'0x351728, comme Base à 0x35172c.
d:\dev\test\hh> _
Ce type d'adresse spécifique est parfois surprenant, même pour chevronné C++ professionnels.
Le changement d'adresse se produit parce que l'adresse Base
objet doit être l'adresse du premier octet de l' Base
sous-objet en Derived
. Et les deux compilateurs utilisés placé au-dessus de l' Base
sous-objet ailleurs que, dès le début de l' Derived
objet. Apparemment, ils ont choisi de placer une vtable pointeur au début, suivie par l' Base
sous-objet; de toute façon, avec l' Base
sous-objet n'est pas au tout début de l' Derived
de l'objet, et donc avec une adresse différente.
En raison de son changement d'adresse, lorsqu'un unique_ptr<Base>
passe sa propriété pointeur (de type Base*
) pour un deleter fonction prenant void*
argument, la deleter fonction peut recevoir une autre adresse, un autre void*
de la valeur, que pour un original Derived*
la valeur du pointeur.
L'effet d'une fonte d' Derived*
peut alors être différente de l'originale Derived*
pointeur, et toute tentative d'utilisation de la visée de l'objet, comme par delete
, puis a un Comportement Indéfini.
Par exemple, le code ci-dessous, à l'aide de la réponse originale à cette question apparemment simple deleter fonction de régime, les accidents:
#include <iostream>
#include <memory> // std::unique_ptr
#include <utility> // std::forward
struct Base { int x; };
struct Derived: Base { virtual ~Derived() {} };
template< class Type >
void deleteFuncImpl( void* p )
{
delete static_cast< Type* >( p );
}
#if defined( USE_CPP11_ARGUMENT_FORWARDING )
template< class T, class ...Args >
std::unique_ptr< T, void(*)(void*) >
make_ptr( Args&& ...args )
{
return std::unique_ptr< T, void(*)(void*) >(
new T( std::forward<Args>( args )... ),
&deleteFuncImpl< T >
);
}
#endif
template< class T >
std::unique_ptr< T, void(*)(void*) > make_ptr()
{
return std::unique_ptr< T, void(*)(void*) >(
new T(),
&deleteFuncImpl< T >
);
}
int main()
{
std::unique_ptr< Base, void(*)(void*) > p = make_ptr< Derived >();
}
Le crash:
d:\dev\test\hh> g++ hh_02.cpp -std=c++0x -o hh_02
d:\dev\test\hh> hh_02
d:\dev\test\hh> _
J'ai donné une solution simple dans mon commentaire comme réponse, cette réponse originale à cette question à partir de Howard était une réponse.
D'origine, y compris mise à jour:A votre question contenait votre réponse, ou du code qui y au moins tenté pour votre réponse, je (ou quelqu'un d'autre) pourrait avoir offert le code ci-dessous comme beaucoup plus simple, plus claire et moins d'obfuscation alternative:
// Your Base and Derived
// ..
template< class Type >
void deleteFuncImpl( void* p )
{
delete static_cast< Type* >( p );
}
int main()
{
unique_ptr< Base, void(*)(void*) > p( new Derived, deleteFuncImpl<Derived> );
cout << p->message() << endl;
}
Base:<init>
Derived::<init>
Message from Derived!
Derived::<destroy>
Base::<destroy>
Parmi ceux qui connaissent unique_ptr
, il est bien connu que l'on peut obtenir de nombreux avantages d'une dynamique deleter à l'aide d'un pointeur de fonction comme la statique de la deleter type, pas besoin que ce soit pour envelopper le pointeur de fonction, ni la deleter dans les classes auxiliaires.
Et notez que cette fonctionnalité est que vous payez seulement si vous l'utilisez. L' sizeof unique_ptr
est le même que pour auto_ptr
et T*
si vous êtes en défaut la deleter, ou spécifier un apatride deleter. Si vous spécifiez un "stateful" deleter (comme un pointeur de fonction), puis de la mémoire supplémentaire est pile-allouée pour stocker l'état de votre deleter.
Naturellement, il convient de souligner que l' default_delete
re, utilisé avec l' unique_ptr<Base>
est apatrides (ajoute pas de frais généraux), si vous choisissez de suivre le temps-testé conception de rendre ~Base
virtuel.
...
virtual ~Base() { cout << "Base::<destroy>" << endl; }
...
int main()
{
unique_ptr< Base > p( new Derived );
cout << p->message() << endl;
assert(sizeof(unique_ptr< Base >) == sizeof(Base*));
}
Base:<init>
Derived::<init>
Message from Derived!
Derived::<destroy>
Base::<destroy>
Le ci-dessus est vraiment le zéro-frais de solution depuis Base
a déjà une fonction virtuelle (message
) et donc de faire des ~Base()
virtuelle ne pas ajouter d'importantes charges supplémentaires pour Base
.
Une dynamique deleter (comme shared_ptr
') a été proposé pour l' unique_ptr
plusieurs fois au cours de normalisation. Et le tout en un unique propriétaire de pointeur intelligent avec une dynamique deleter ne avoir des cas d'utilisation, c'est trop pour les cas d'utilisation - unique_ptr
a été conçu pour. L'un des principaux cas d'utilisation pour l' unique_ptr
est sécuritaire de le remplacer auto_ptr
. Si unique_ptr
avaient une dynamique deleter, il aurait surcharge, au-dessus de auto_ptr
, et n'a donc pas été capable de la remplacer entièrement auto_ptr
. Et nous, par la suite, aurait été incapable de rendre caduque auto_ptr
et de son cortège de risque d'erreur de l'utiliser dans le code générique (déplacement avec copie de la syntaxe).
Mise à jour
Il ya une certaine inquiétude de l'utilisation de void*
dans la fonction qui fait la suppression. Un moyen de corriger cela est de rendre le paramètre Type*
:
template< class Type >
void deleteFuncImpl( Type* p )
{
delete p;
}
Cela ne fonctionne pas dans le cas des OP code parce que l' unique_ptr<Derived>
est converti en unique_ptr<Base>
et par la suite seront de passage à un Base*
d'un Derived*
à la destruction du temps, qui n'est pas le convertir implicitement.
Une autre solution serait de donner des deleteFuncImpl
deux paramètres:
template<class Base, class Type >
void deleteFuncImpl( Base* p )
{
delete static_cast< Type* >( p );
}
Mais c'est ridiculement maladroit, et pas vraiment mieux que l'original void*
de la solution.
Enfin, on peut noter que l'on ne peut pas avoir un comportement indéfini si dans cette déclaration:
unique_ptr< Base, void(*)(void*) > p( new Derived, deleteFuncImpl<Derived> );
^^^^^^^ ^^^^^^^
les deux souligné les types sont les mêmes. La meilleure façon de faire respecter c'est de créer une usine de la fonction:
template <class T, class ...Args>
std::unique_ptr< T, void(*)(void*) >
make_ptr(Args&& ...args)
{
return std::unique_ptr<T, void(*)(void*)>
(new T(std::forward<Args>(args)...),
deleteFuncImpl<T>);
}
qui peut être utilisée comme ceci:
std::unique_ptr< Base, void(*)(void*) > p = make_ptr<Derived>();
Maintenant, même si deleteFuncImpl
prend son paramètre par void*
, nous savons a priori que le temps d'exécution type d' deleteFuncImpl<T>
argument est un T*
, ce qui élimine la possibilité d'un décalage entre le moment de l'exécution le type de l'objet, et le static_cast au sein de la deleter fonction.
Dans cette mise à jour de la solution, l' sizeof unique_ptr
est deux mots. Je tiens à souligner une fois de plus, cependant, que la solution optimale, à la fois dans l'espace et de la clarté de codage, est de donner de l' Base
un destructeur virtuel. Dans cette solution l' sizeof unique_ptr
est un mot, le même que pour un pointeur brut.
La fonction de pointeur de-la deleter solution doit être réservée aux cas où une virtual ~Base()
n'est pas significatif. Les exemples incluent l'utilisation de std::free
et std::close
que la deleter.
Enfin, sur la suggestion d'utiliser std::function<void(something-I'm-not-sure-what)>
:
Un std::function<void(something-I'm-not-sure-what)>
peut être "null". Et donc un pointeur de fonction. Toutefois unique_ptr
est spécialement formulée pour faire accidentellement à l'attribution d'une fonction nulle pointeur peu probable, alors qu'il n'a pas de tels protocoles de sécurité en place pour protéger contre une valeur null std::function
. Cette compile et se bloque au moment de l'exécution:
std::unique_ptr<int, std::function<void(void*)>> p(new int(3));
terminate called without an active exception
Abort trap: 6
Mais cette attrape l'erreur au moment de la compilation:
std::unique_ptr<int, void(*)(void*)> p(new int(3));
error: static_assert failed "unique_ptr constructed with null function pointer deleter"
En outre, std::function
ajoute une charge sur un pointeur de fonction comme indiqué par Johannes Schaub dans les commentaires de cette réponse.
std::function
non seulement ajoute de la vitesse et de la taille probable de frais généraux par rapport à un pointeur de fonction, mais dans ce cas est également plus sujettes à erreur.