unique_ptr
11 avril 2023
Lors de l’étape 2 de l’exercice Outil de visualisation, il se peut
que vous ayez rencontré ou non des soucis à convertir vos pointeurs nus
en unique_ptr
.
Les paragraphes suivants présentent quelques soucis classiques lors
de l’utilisation des unique_ptr
avec les symptômes, des
propositions de solution et un exemple pour faire apparaître le
souci.
unique_ptr
Dans le cas où votre code tente de faire une copie directe d’un
unique_ptr
, la compilation de votre code vous affiche un
message comme :
.../unitTests/unitTests.cpp:70:19: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Group; _Dp = std::default_delete<Group>]’
70 | auto pg2 { pg };
...\unitTests\unitTests.cpp(70): error C2280: 'std::unique_ptr<Group,std::default_delete<Group>>::unique_ptr(const std::unique_ptr<Group,std::default_delete<Group>> &)' : tentative de référencement d'une fonction supprimée
Allez voir la ligne de votre source indiquée par le compilateur (par
exemple, ligne 70 de unitTests.cpp
dans les 2 messages
ci-dessus) : Elle contient une copie d’un unique_ptr
.
Voici quelques pistes de solutions (à vous de trouver celle qui convient à votre code !) :
std::move()
?unique_ptr
(méthode get()
) ?unique_ptr
en paramètre d’une méthode, peut-être pouvez-vous dire que ce paramètre
est en fait une référence ?Voici un exemple pour faire apparaître le souci. Dans votre fichier
unitTests.cpp
, ajoutez #include <memory>
et le test suivant :
(TestAnalyseXML, Test_copy_unique_ptr) {
TESTstd::string s = R"(<?xml version = "1.0"?>
<Group label="testGroupHybrid" x="0" y="1">
<Circle label="testCircle" x="2" y="3" r="4" color="Black"/>
<Group label="testGroup" x="5" y="6"/>
</Group>)";
::xml_document doc;
pugi::xml_parse_result result = doc.load_string(s.c_str());
pugi(result) << result.description(); // Si jamais result est faux, indique que le test est faux *et* affiche la string result.description() (qui contient la raison de l'erreur)
ASSERT_TRUEauto pg = std::make_unique<Group>( doc.child("Group") );
auto pg2 { pg };
std::string g_dump_ref =
R"(Group "testGroupHybrid", x: 0, y: 1, children: [
| Circle "testCircle", x: 2, y: 3, r: 4, color: "Black"
| Group "testGroup", x: 5, y: 6, children: [
| ]
]
)";
(pg->dump(), g_dump_ref);
EXPECT_EQ}
Si vous essayez de compiler, vous devriez avoir un message d’erreur
similaire au message ci-dessus, ce qui vous permet de détecter que le
souci vient de l’instruction auto pg2 { pg };
qui cherche à
créer un unique_ptr pg2
en faisant une copie du
unique_ptr pg
, ce qui est interdit.
Nous avons vu précédemment 3 pistes de solutions. Toutefois, pour
pouvoir illustrer un autre souci classique avec les
unique_ptr
(cf. section suivante), nous vous proposons de
corriger le souci en utilisant std::move()
et donc en
remplaçant auto pg2 { pg };
par
auto pg2 { std::move(pg) };
unique_ptr
qu’on s’est engagé à ne plus utiliser avec
std::move()
Dans le cas où votre code tente d’utiliser un unique_ptr
qu’il s’est engagé à ne plus utiliser (votre code a fait un
std::move()
sur ce unique_ptr
), votre code
compile, mais vous aurez un plantage à l’exécution au niveau de la ligne
qui utilise le unique_ptr
qui a subi un
std::move()
.
NB : Avec l’IDE Clion (et avec le compilateur Clang ?), l’IDE vous affiche un warning “Clang-Tidy: ‘nom de votre unique_ptr’ used after it was moved”.
Voici quelques pistes de solutions (à vous de trouver celle qui convient à votre code !) :
0x00...00 <NULL>
.
std::move()
sur votre unique_ptr
vous pouvez
mettre en place une surveillance par le debugger de votre
unique_ptr
unique_ptr
Par exemple, si vous exécutez le code ajouté à la section précédente,
votre programme plantera. Le debugger vous permettra de voir que c’est
au niveau de la ligne EXPECT_EQ(pg->dump(), g_dump_ref);
qui travaille avec pg
sur lequel vous avez fait
std::move(pg)
quelques lignes plus haut.
Pour corriger le problème, vous pouvez :
EXPECT_EQ(pg->dump(), g_dump_ref);
par
EXPECT_EQ(pg2->dump(), g_dump_ref);
(notez le `pg2)auto pg2 { std::move(pg) };
qui n’a
fait qu’apporter des soucis (mais a permis d’en apprendre plus sur les
unique_ptr
.TEST(TestAnalyseXML, Test_unique_ptr)
qui est
fonctionnellement redondant avec
TEST(TestAnalyseXML, TestGroupHybrid)
unique_ptr
Parfois, la tentative de copie d’un unique_ptr
ne se
fait pas de manière directe, mais par exemple en tentant de copier un
objet dont une donnée membre est un unique_ptr
. Un tel
objet n’est pas copiable, mais hélas le message des compilateurs à ce
sujet n’est pas forcément des plus utiles. Ils indiquent généralement
qu’une tentative de copie a eu lieu, mais sans dire où. Une astuce pour
forcer le compilateur à être plus explicite consiste à interdire
explicitement la copie de la classe. Il vous indiquera alors
l’emplacement des tentatives de copie. Parmi les messages d’erreurs
initiaux du compilateur, repérez celui où il évoque une de vos classes :
C’est la classe pour laquelle votre code fait une copie d’instance, ce
qui entraîne une tentative de duplication du unique_ptr
qu’elle contient.
Pour retrouver la ligne à laquelle se passe cette copie, ajoutez la
définition suivante (qui indique que l’appel au constructeur de copie de
MaClasse
est interdit) à MaClasse.h
:
(const MaClasse &) = delete; MaClasse
Relancez la génération de votre exécutable : Le compilateur affiche désormais un message d’erreur mentionnant explicitement la ligne à laquelle se passe votre copie.
Voici quelques pistes de solutions (à vous de trouver celle qui convient à votre code !) :
std::move()
?Voici un exemple pour faire apparaître le souci. Dans votre fichier
unitTests.cpp
, ajoutez #include <memory>
et le test suivant :
(TestAnalyseXML, Test_hidden_copy_unique_ptr) {
TESTstd::string s = R"(<?xml version = "1.0"?>
<Group label="testGroupHybrid" x="0" y="1">
<Circle label="testCircle" x="2" y="3" r="4" color="Black"/>
<Group label="testGroup" x="5" y="6"/>
</Group>)";
::xml_document doc;
pugi::xml_parse_result result = doc.load_string(s.c_str());
pugi(result) << result.description(); // Si jamais result est faux, indique que le test est faux *et* affiche la string result.description() (qui contient la raison de l'erreur)
ASSERT_TRUE{ doc.child("Group") };
Group g{ g };
Group g2std::string g_dump_ref =
R"(Group "testGroupHybrid", x: 0, y: 1, children: [
| Circle "testCircle", x: 2, y: 3, r: 4, color: "Black"
| Group "testGroup", x: 5, y: 6, children: [
| ]
]
)";
(g.dump(), g_dump_ref);
EXPECT_EQ}
Si vous essayez de compiler, vous obtenez de nombreux messages dont voici un extrait :
In file included from /usr/include/c++/11/memory:66,
from .../cmake-build-debug/unitTests/googletest-src/googletest/include/gtest/gtest.h:56,
from .../unitTests/unitTests.cpp:1:
/usr/include/c++/11/bits/stl_uninitialized.h: In instantiation of ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<Shape>*, std::vector<std::unique_ptr<Shape> > >; _ForwardIterator = std::unique_ptr<Shape>*]’:
/usr/include/c++/11/bits/stl_uninitialized.h:333:37: required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const std::unique_ptr<Shape>*, std::vector<std::unique_ptr<Shape> > >; _ForwardIterator = std::unique_ptr<Shape>*; _Tp = std::unique_ptr<Shape>]’
/usr/include/c++/11/bits/stl_vector.h:558:31: required from ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = std::unique_ptr<Shape>; _Alloc = std::allocator<std::unique_ptr<Shape> >]’
.../src/./Group.h:6:7: required from here
/usr/include/c++/11/bits/stl_uninitialized.h:138:72: error: static assertion failed: result type must be constructible from value type of input range
138 | static_assert(is_constructible<_ValueType2, decltype(*__first)>::value,
| ^~~~~
/usr/include/c++/11/bits/stl_uninitialized.h:138:72: note: ‘std::integral_constant<bool, false>::value’ evaluates to false
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\vector(745): note: pendant la compilation de la fonction membre classe modèle 'std::vector<std::unique_ptr<Shape,std::default_delete<Shape>>,std::allocator<std::unique_ptr<Shape,std::default_delete<Shape>>>>::vector(const std::vector<std::unique_ptr<Shape,std::default_delete<Shape>>,std::allocator<std::unique_ptr<Shape,std::default_delete<Shape>>>> &)'
...\src\Group.h(14): note: voir la référence à l'instanciation de la fonction modèle 'std::vector<std::unique_ptr<Shape,std::default_delete<Shape>>,std::allocator<std::unique_ptr<Shape,std::default_delete<Shape>>>>::vector(const std::vector<std::unique_ptr<Shape,std::default_delete<Shape>>,std::allocator<std::unique_ptr<Shape,std::default_delete<Shape>>>> &)' en cours de compilation
...\src\Group.h(13): note: voir la référence à l'instanciation classe modèle 'std::vector<std::unique_ptr<Shape,std::default_delete<Shape>>,std::allocator<std::unique_ptr<Shape,std::default_delete<Shape>>>>' en cours de compilation
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xutility(261): error C2280: 'std::unique_ptr<Shape,std::default_delete<Shape>>::unique_ptr(const std::unique_ptr<Shape,std::default_delete<Shape>> &)' : tentative de référencement d'une fonction supprimée
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\memory(3216): note: voir la déclaration de 'std::unique_ptr<Shape,std::default_delete<Shape>>::unique_ptr'
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\memory(3216): note: 'std::unique_ptr<Shape,std::default_delete<Shape>>::unique_ptr(const std::unique_ptr<Shape,std::default_delete<Shape>> &)' : la fonction a été supprimée explicitement
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\xmemory(680): error C2280: 'std::unique_ptr<Shape,std::default_delete<Shape>>::unique_ptr(const std::unique_ptr<Shape,std::default_delete<Shape>> &)' : tentative de référencement d'une fonction supprimée
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\memory(3216): note: voir la déclaration de 'std::unique_ptr<Shape,std::default_delete<Shape>>::unique_ptr'
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include\memory(3216): note: 'std::unique_ptr<Shape,std::default_delete<Shape>>::unique_ptr(const std::unique_ptr<Shape,std::default_delete<Shape>> &)' : la fonction a été supprimée explicitement
Quel que soit le compilateur, les messages évoquent
Group.h
. Donc, nous modifions Group.h
en
ajoutant :
(const Group &) = delete; Group
En relançant la compilation, le compilateur affiche désormais le message :
...\unitTests\unitTests.cpp(70): error C2280: 'Group::Group(const Group &)' : tentative de référencement d'une fonction supprimée
Cela vous permet de retrouver l’instruction qui pose souci :
Group g2{ g };
NB :
Group g2{ std::move(g) };
, l’instruction
EXPECT_EQ(g.dump(), g_dump_ref);
qui suit ne fera pas
planter votre programme à l’exécution (comme cela arrivait lors du
std::move()
d’un unique_ptr
).Clang-Tidy: 'g' used after it was moved