2017-10-13 3 views
3

Imaginez que je souhaite construire un std::vector d'objets sans constructeur de mouvement ou de copie, tel que std::atomic<int>. Dans ce cas, la classe std::atomic sous-jacente a un constructeur à 1 argument qui prend un int, ainsi qu'un constructeur par défaut (qui initialise la valeur à 0).Construction semblable à un emplacement pour std :: vector

Utilisation du std::vector<std::atomic<int>> v{1,2,3} comme ne fonctionne pas la syntaxe initializer_list, parce que les arguments sont d'abord convertis au type d'élément T du vecteur dans le cadre de la création du initializer_list et ainsi la copie ou le constructeur déplacer sera appelée.

Dans le cas particulier de std::atomic<int> je peux DEFAULT construire le vecteur, puis muter les éléments après:

std::vector<std::atomic<int>> v(3); 
v[0] = 1; 
v[1] = 2; 
v[2] = 3; 

Cependant, en plus d'être laid et inefficace, ce n'est pas une solution générale puisque beaucoup Les objets peuvent ne pas offrir une mutation post-construction équivalente à ce que vous pourriez obtenir en appelant le constructeur approprié.

Y a-t-il un moyen d'obtenir le comportement "emplace-like" que je veux à la construction vectorielle?

+2

Sérieusement, je voudrais juste utiliser 'std :: deque'. Mais si vous ne pouvez pas, la seule façon de faire ce que vous voulez est à travers un allocateur personnalisé. – Brian

+0

@Brian - Est-ce que 'std :: deque' permet l'idiome de cette construction? – BeeOnRope

+1

Avec 'std :: deque' vous devrez placer les éléments un par un, mais ça marchera car ajouter des éléments au début ou à la fin ne déplace aucun des autres éléments. – Brian

Répondre

1

Une solution générale consiste à faire prendre à votre vecteur un allocateur personnalisé dont la méthode construct effectue l'initialisation appropriée. Dans le code ci-dessous, v utilise l'allocateur MyAllocator<NonMovable> au lieu de std::allocator<NonMovable>. Lorsque la méthode construct est appelée sans arguments, elle appelle le constructeur avec l'argument approprié. De cette façon, le constructeur par défaut peut initialiser les éléments correctement.

(Pour simplicitly, je me suis fait next_value statique dans cet exemple, mais il pourrait tout aussi bien être une variable membre non statique qui est initialisé lors MyAllocator est construit.)

#include <stdio.h> 
#include <memory> 
#include <new> 
#include <vector> 

struct NonMovable { 
    NonMovable(int x) : x(x) {} 
    const int x; 
}; 

template <class T> 
struct MyAllocator { 
    typedef T value_type; 
    static int next_value; 
    T* allocate(size_t n) { 
     return static_cast<T*>(::operator new(n * sizeof(T))); 
    } 
    void deallocate(T* p, size_t n) { 
     ::operator delete(p); 
    } 
    template <class U> 
    void construct(U* p) { 
     new (p) U(++next_value); 
    } 
}; 

template <class T> int MyAllocator<T>::next_value = 0; 

int main() { 
    std::vector<NonMovable, MyAllocator<NonMovable>> v(10); 
    for (int i = 0; i < 10; i++) { 
     printf("%d\n", v[i].x); 
    } 
} 

http://coliru.stacked-crooked.com/a/1a89fddd325514bf

C'est la seule solution possible lorsque vous n'êtes pas autorisé à toucher la classe NonMovable et que son constructeur peut nécessiter plusieurs arguments. Dans le cas où il vous suffit de passer un argument à chaque constructeur, il y a une solution beaucoup plus simple qui utilise le constructeur de gamme de std::vector, comme ceci:

std::vector<int> ints(10); 
std::iota(ints.begin(), ints.end(), 1); 
std::vector<NonMovable> v(ints.begin(), ints.end()); 

(Bien que si vous ne pouvez pas la mémoire supplémentaire , alors vous devrez écrire un itérateur personnalisé, qui sera beaucoup plus de code.)