dimanche 17 juillet 2011

C++0x - std::unique_ptr et types partiels

Prenons un exemple très simple: l'idiome "pimpl" (dont le nom m'horripile, mais bon), encore appelé "pointeur opaque". Dans le .h, on forward déclare notre type interne, que l'on définit dans le .cpp. L'idée est de cacher l'implémentation, ce qui a également l'avantage de permettre une compilation plus rapide en évitant à l'interface l'inclusion d'une myriade d'en têtes pour les types privés.

Là, en bon dev C++11, l'on se dit, chic, je vais utiliser un std::unique_ptr. Voyons ce que cela donne. Ici, l'on a la classe Impl, forward déclarée, utilisée dans un std::unique_ptr, suivi d'un bout de code confirmant au compilo que l'on a bien l'intention d'instancier la classe Interface:


#include <memory>

class Impl;

class Interface
{
public:
Interface();

private:
std::unique_ptr<Impl> m_impl;
};

void create()
{
Interface i;
}

L'on est immédiatement couvert d'injures par le compilateur:

g++ -std=gnu++0x -c uniq.cpp
In file included from /usr/include/c++/4.6/memory:85:0,
from uniq.cpp:1:
/usr/include/c++/4.6/bits/unique_ptr.h: In member function ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Impl]’:
/usr/include/c++/4.6/bits/unique_ptr.h:245:4: instantiated from ‘void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = Impl, _Dp = std::default_delete, std::unique_ptr<_Tp, _Dp>::pointer = Impl*]’
/usr/include/c++/4.6/bits/unique_ptr.h:169:23: instantiated from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Impl, _Dp = std::default_delete]’
uniq.cpp:5:7: instantiated from here
/usr/include/c++/4.6/bits/unique_ptr.h:63:14: error: invalid application of ‘sizeof’ to incomplete type ‘Impl’


Rajoutons juste un destructeur:

#include <memory>

class Impl;

class Interface
{
public:
Interface();
~Interface();

private:
std::unique_ptr<Impl> m_impl;
};

void create()
{
Interface i;
}

Et g++ nous claque deux bises.

En effet, et d'après ce tableau, contrairement à std::shared_ptr, std::unique_ptr ne peut être détruit que si son type sous-jacent est complètement défini. Dans le premier exemple, le destructeur n'est pas défini, et g++ en définit donc un par défaut, inline. Il n'a donc pas accès au type Impl, ce qui cause l'erreur. Par contre, dans le deuxième exemple, l'on a décidé de fournir un destructeur quelque part ailleurs. Le compilateur n'a donc pas besoin de générer le code pour détruire notre pointeur. Celui-ci sera généré à la définition du destructeur, probablement dans le fichier source, et donc probablement après la définition complète d'Impl.

Aucun commentaire: