Comment stocker des objets sans copier ou déplacer le constructeur dans std:: vector?
pour améliorer l'efficacité de std::vector<T>
, son réseau sous-jacent doit être pré-alloué et parfois ré-alloué. Cela nécessite toutefois la création et le déplacement ultérieur d'objets de type T
avec un ctor de copie ou un ctor de déplacement.
le problème que j'ai est que T
ne peut pas être copié ou déplacé parce qu'il contient des objets qui ne peuvent pas être copiés ou déplacés (tels que atomic
et mutex
). (Et, oui, je me suis mise en œuvre d'une simple pool de threads.)
je voudrais éviter d'utiliser des pointeurs parce que:
- je n'ai pas besoin d'un niveau d'indirection et donc je n'en veulent pas.
- (les pointeurs sont moins efficaces et augmentent la complexité. L'utilisation de pointeurs augmente la fragmentation de la mémoire et diminue la localisation des données, ce qui peut (mais ne doit pas nécessairement) avoir un impact notable sur les performances. Pas si important, mais ça vaut la peine d'être considéré.)
Est-il un moyen d'éviter un niveau d'indirection ici?
mise à jour: j'ai corrigé certaines hypothèses inexactes et reformulé la question en fonction des commentaires et des réponses.
4 réponses
Pour le début, std::mutex
ne peut pas être copié ou déplacé, donc vous êtes obligé d'utiliser une sorte d'indirection.
puisque vous voulez stocker mutex dans un vecteur, et ne pas le copier, j'utiliserais std::unique_ptr
.
vector<unique_ptr<T>>
ne permet pas certaines opérations vectorielles (telles que for_each)
je ne suis pas sûr de comprendre cette phrase. Il est parfaitement possible de faire gamme pour:
std::vector< std::unique_ptr< int > > v;
// fill in the vector
for ( auto & it : v )
std::cout << *it << std::endl;
ou pour utiliser des algorithmes std:
#include <iostream>
#include <typeinfo>
#include <vector>
#include <memory>
#include <algorithm>
int main()
{
std::vector< std::unique_ptr< int > > v;
v.emplace_back( new int( 3 ) );
v.emplace_back( new int( 5 ) );
std::for_each( v.begin(), v.end(), []( const std::unique_ptr< int > & it ){ std::cout << *it << std::endl; } );
}
qui nécessite toutefois la création d'objets de type T avec un ctor de copie.
ce n'est pas tout à fait juste, à partir de C++11, Si vous utilisez le constructeur de std::vector
qui construira par défaut un certain nombre d'éléments, alors vous n'avez pas besoin d'avoir une copie ou un constructeur de déplacement.
en tant que tel, si aucun fil n'est ajouté ou supprimé de votre pool, vous pouvez alors simplement faire:
int num = 23;
std::vector<std::mutex> vec(num);
Si vous voulez ajouter ou supprimer des choses dynamiquement, alors vous devez utiliser une expression indirecte.
- Utiliser
std::vector
+std::unique_ptr
comme déjà proposé - utilisez un
std::deque
, qui vous permet de l'utiliser correctement avec une portée basée sur des boucles ou des algorithmes std et évite toutes les indirectes. Milliard cinq cent dix neuf million deux cent vingt mille neuf cent vingt" - Utiliser un
std::list/forward_list
cette solution est similaire à numéro un, mais il a l'avantage supplémentaire de utilisation plus facile avec gamme basée pour et algorithmes. C'est probablement le mieux si vous accédez seulement aux éléments séquentiellement car il n'y a pas de support pour l'accès aléatoire.
comme ceci:
std::deque<std::mutex> deq;
deq.emplace_back();
deq.emplace_back();
for(auto& m : deq) {
m.lock();
}
comme note finale, std::thread
est bien sûr mobile, donc vous pouvez utiliser std::vector
+ std::vector::emplace_back
avec elle.
pour résumer ce qui a été proposé jusqu'à présent:
- Use
vector<unique_ptr<T>>
-- ajoute un niveau explicite d'indirectement et a été non désirée par OP. - utiliser
deque<T>
-- j'ai d'abord essayédeque
, mais effacer des objets de celui-ci ne fonctionne pas non plus. voir cette discussion sur les différences entredeque
etlist
.
la solution est d'utiliser forward_list
qui est une liste liée individuellement (ou vous pouvez utiliser list
si vous voulez une liste à double liaison). Comme @JanHudec l'a souligné, vector
(et beaucoup de ses amis) nécessite une réattribution lors de l'ajout ou de la suppression d'articles. Cela ne convient pas aux objets comme mutex
et atomic
qui ne sont pas autorisés à être copiés ou déplacés. forward_list
et list
n'exigent pas que parce que chaque cellule est attribuée indépendamment (Je ne peux pas citer la norme, mais l'indexation méthode donne lieu à cette hypothèse). Comme il s'agit en fait de listes liées, elles ne prennent pas en charge l'indexation par accès aléatoire. myList.begin() + i
vous obtiendra un itérateur du i
'e élément, mais il (très certainement) aura à boucle à travers toutes les précédentes i
cellules d'abord.
Je n'ai pas regardé les promesses du standard, mais les choses fonctionnent bien sur Windows (Visual Studio) et CompileOnline (g++). Hésitez pas à jouer avec le cas d'essai suivant sur CompileOnline :
#include <forward_list>
#include <iostream>
#include <mutex>
#include <algorithm>
using namespace std;
class X
{
/// Private copy constructor.
X(const X&);
/// Private assignment operator.
X& operator=(const X&);
public:
/// Some integer value
int val;
/// An object that can be neither copied nor moved
mutex m;
X(int val) : val(val) { }
};
int main()
{
// create list
forward_list<X> xList;
// add some items to list
for (int i = 0; i < 4; ++i)
xList.emplace_front(i);
// print items
for (auto& x : xList)
cout << x.val << endl;
// remove element with val == 1
// WARNING: Must not use remove here (see explanation below)
xList.remove_if([](const X& x) { return x.val == 1; });
cout << endl << "Removed '1'..." << endl << endl;
for (auto& x : xList)
cout << x.val << endl;
return 0;
}
sortie:
Executing the program....
$demo
3
2
1
0
Removed '1'...
3
2
0
je m'attends à peu près avoir le même rendement que vector<unique_ptr<T>>
(tant que vous n'utilisez pas d'accès aléatoire indexation trop souvent).
WARNING : L'utilisation de forward_list::remove
ne fonctionne pas actuellement dans VS 2012. C'est parce qu'elle copie l'élément avant d'essayer de les supprimer il. Le fichier d'en-tête Microsoft Visual Studio 11.0\VC\include\forward_list
(même problème dans list
) révèle:
void remove(const _Ty& _Val_arg)
{ // erase each element matching _Val
const _Ty _Val = _Val_arg; // in case it's removed along the way
// ...
}
ainsi, il est copié "au cas où il est enlevé en cours de route". Cela signifie que list
et forward_list
ne permettent même pas de stocker unique_ptr
. Je suppose que c'est un bogue du design.
la solution est simple: vous devez utiliser remove_if
au lieu de remove
parce que la mise en œuvre de cette fonction ne copie rien.
une grande partie du crédit va aux autres réponses. Cependant, comme aucune d'entre elles n'était une solution complète sans pointeurs, j'ai décidé d'écrire cette réponse.
vous peut stocker des éléments sans se déplacer ou copier des constructeurs dans std::vector
, mais vous devez tout simplement éviter les méthodes qui exigent que les éléments ont des constructeurs se déplacer ou copier. Presque 1 tout ce qui change la taille du vecteur (par exemple, push_back
, resize()
, etc).
dans la pratique, cela signifie que vous devez allouer un vecteur de taille fixe au moment de la construction, qui invoquera le constructeur par défaut de vos objets, que vous pouvez modifier avec affectation. Cela pourrait au moins fonctionner pour les objets std::atomic<>
, qui peuvent être assignés.
1 clear()
est le seul exemple d'une méthode de changement de taille qui ne nécessite pas de constructeur de copie/déplacement, puisqu'elle n'a jamais besoin de déplacer ou de copier des éléments (après tout, le vecteur est vide après cette opération). Bien sûr, vous ne pourrez plus jamais faire pousser votre vecteur de taille zéro après l'appel de cette!