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 :
TEST(TestAnalyseXML, Test_copy_unique_ptr) {std::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>)";
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(s.c_str());// 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(result) << result.description(); auto 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: [
| ]
]
)";
g_dump_ref);
EXPECT_EQ(pg->dump(), }
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 :
TEST(TestAnalyseXML, Test_hidden_copy_unique_ptr) {std::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>)";
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(s.c_str());// 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(result) << result.description(); "Group") };
Group g{ doc.child(
Group g2{ g };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: [
| ]
]
)";
g_dump_ref);
EXPECT_EQ(g.dump(), }
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