2023-10-02 10:06:00
Dans mon dernier article « Développement logiciel : allocateurs polymorphes avec C++17 » j’ai présenté la théorie des allocateurs polymorphes en C++17. Aujourd’hui, je vais appliquer la théorie.
Publicité
Rainer Grimm travaille depuis de nombreuses années en tant qu’architecte logiciel, responsable d’équipe et de formation. Il aime écrire des articles sur les langages de programmation C++, Python et Haskell, mais aime également intervenir fréquemment lors de conférences spécialisées. Sur son blog Modern C++, il parle intensément de sa passion C++.
Avant de continuer, voici les parties les plus importantes de mon dernier article « Développement de logiciels : allocateurs polymorphes avec C++17 ».
Un petit rappel
Le programme suivant utilise des allocateurs polymorphes :
// polymorphicAllocator.cpp
#include
#include
#include
#include
int main() {
std::array buf1; // (1)
std::pmr::monotonic_buffer_resource pool1{buf1.data(),
buf1.size()};
std::pmr::vector myVec1{&pool1}; // (3)
for (int i = 0; i < 5; ++i) {
myVec1.push_back(i);
}
char buf2[200] = {}; // (2)
std::pmr::monotonic_buffer_resource pool2{std::data(buf2),
std::size(buf2)};
std::pmr::vector myVec2{&pool2};
for (int i = 0; i < 200; ++i) {
myVec2.push_back(i);
}
}
Maintenant je veux me lever myVec2
se concentrer. 200 int
s sera sur le std::pmr::vector
poussé. Ces 200 int
ça ne rentre pas dans un char buf[200]
et donc saute std::pmr::new_delete_resource()
en tant qu'allocateur en amont et appelle le global new
pour les éléments restants. Je vais maintenant instrumentaliser l'allocateur amont.
Un allocateur de suivi
Le programme suivant est basé sur le précédent, utilise un allocateur de suivi et rend visibles l'allocation et la désallocation dynamiques de mémoire.
// trackAllocator.cpp
#include
#include
#include
#include
#include
#include
class TrackAllocator : public std::pmr::memory_resource {
void* do_allocate(std::size_t bytes,
std::size_t alignment) override {
void* p = std::pmr::new_delete_resource()->
allocate(bytes, alignment);
std::cout <<
std::format(" do_allocate: {:6} bytes at {}n",
bytes, p);
return p;
}
void do_deallocate(void* p,
std::size_t bytes,
std::size_t alignment) override {
std::cout <<
std::format(" do_deallocate: {:4} bytes at {}n",
bytes, p);
return
std::pmr::new_delete_resource()->
deallocate(p, bytes, alignment);
}
bool do_is_equal(const std::pmr::memory_resource& other)
const noexcept override {
return std::pmr::new_delete_resource()->is_equal(other);
}
};
int main() {
std::cout << 'n';
TrackAllocator trackAllocator; // (1)
std::pmr::set_default_resource(&trackAllocator); // (2)
std::cout << "myVec1n";
std::array buf1;
std::pmr::monotonic_buffer_resource pool1{buf1.data(),
buf1.size()};
std::pmr::vector myVec1{&pool1}; // (3)
for (int i = 0; i < 5; ++i) {
myVec1.push_back(i);
}
std::cout << "myVec2n";
char buf2[200] = {};
std::pmr::monotonic_buffer_resource pool2{std::data(buf2),
std::size(buf2)};
std::pmr::vector myVec2{&pool2}; // (4)
for (int i = 0; i < 200; ++i) {
myVec2.push_back(i);
}
std::cout << 'n';
}
TrackAllocator
est un allocateur pour suivre l'allocation de mémoire et sa libération. Il dérive de la classe d'interface std::pmr::memory_resource
à partir duquel toutes les ressources de stockage sont dérivées. TrackAllocator
définit les trois fonctions membres requises do_allocate
, do_deallocate
et do_is_equal
. Chaque appel le redirige vers l'appel de std::pmr::new_delete_resource
plus loin. std:pmr::new_delete_resource
est la ressource de stockage par défaut et appelle le global new
et delete
sur. La tâche de la fonction membre do_is_equal
est de vérifier si les deux ressources mémoire sont égales. Le point intéressant de l'exemple est que j'alloue et désalloue la mémoire dans les fonctions membres do_allocate
et do_deallocate
visualisière.
Les concepts introduits avec C++20, ainsi que la bibliothèque, les modules et les coroutines Ranges, ont redéfini la façon de créer des applications C++ modernes. Du 7 au 9 novembre 2023 Rainer Grimm vous emmène dans son atelier intensif C++20 : les nouveaux concepts expliqués en détail à jour et aborde les nombreuses fonctions utiles apportées par C++20.
J'instancie cela TrackAllocator
(ligne 1) et faites-en la ressource par défaut (2). myVec1
(3) et myVec2
(4) utilisez-le comme allocateur en amont. Cet allocateur intervient lorsque l'allocateur principal est consommé. Cette solution de secours est destinée myVec1
pas nécessaire pour myVec2
mais bien.
Cette sortie montre l'allocation dynamique et la libération de myVec2
bis std::pmr::vector
à int
s peuvent accueillir.
Un allocateur en amont peut également être lié à un conteneur spécifique.
Un allocateur non allouant
std::pmr::null_resource_allocator
est un allocateur spécial. Leur utilisation pour l'allocation donne lieu à un std::bad_alloc
-Exception. Cette ressource mémoire garantit que la mémoire n'est pas allouée de manière aléatoire sur le tas :
// nullMemoryResource.cpp
#include
#include
#include
#include
#include
#include
int main() {
std::cout << 'n';
std::array buf;
std::pmr::monotonic_buffer_resource pool{buf.data(),
buf.size(), // (1)
std::pmr::null_memory_resource()};
std::pmr::vector myVec{&pool}; // (2)
try {
for (int i = 0; i < 100; ++i) { // (3)
std::cerr << i << " ";
myVec.emplace_back("A short string");
}
}
catch (const std::bad_alloc& e) { // (4)
std::cerr << 'n' << e.what() << 'n';
}
std::cout << 'n';
}
J'alloue d'abord de la mémoire sur la pile et j'initialise std::pmr::monotonic_buffer_resource
avec ça. Ensuite, j'utilise cette ressource mémoire et std::pmr::null_memory_resource
en tant qu'allocateur en amont (1). Dans (2) j'en crée un std::pmr::vector
. Puisque j'en ai un std::pmr::string
la chaîne utilise également la ressource mémoire et son allocateur en amont. Si je std::pmr::vector
utiliser, utiliser std::string
les répartiteurs globaux new
et delete
. Enfin, je crée 100 chaînes en (3) et en attrape une en (4). std::bad_alloc
-Exception. J'ai eu ce que je méritais : 100 cordes ne rentrent pas dans une seule std::array
-Tampon. Après 17 chaînes, le tampon est épuisé.
Et après?
Le std::pmr::monotonic_buffer
possède d'excellentes propriétés. C'est assez rapide et ne libère pas de mémoire. Mon prochain article fournira les chiffres.
(moi)
#Développement #logiciel #allocateurs #spéciaux #avec #C17
1696369019