9
votes

Puis-je détacher un std :: vecteur à partir des données qu'il contient?

Je travaille avec une fonction qui donne quelques données en tant que std :: vecteur et une autre fonction (Pensez à l'API héritée) qui traite les données et prend un Const Char *, taille_t len ​​. Y a-t-il un moyen de détacher les données du vecteur de sorte que le vecteur puisse sortir de la portée avant d'appeler la fonction de traitement sans copier les données contenues dans le vecteur (c'est ce que je veux impliquer avec Détachement ).

Certains croquis de code pour illustrer le scénario: xxx


3 commentaires

Pourquoi ne pas simplement garder le vecteur dans la fonction de fonction et appelez processdata (& data [0], data.size ()) ??


@David Rodríguez - Dribesas: Dans Ce code Sketch , la portée de ProcessData () est l'ancêtre direct de la portée des données . Cependant, dans les bases de code mondial réelles, il pourrait une douzaine de cadres de pile entre les deux cordes. Ou imaginez si processdata posterait le pointeur tampon donné et la longueur sur un autre thread, puis retournez immédiatement - vous devez vous assurer que les données restent valables jusqu'à ce que le fil du consommateur le traite. Ce ne sont que deux exemples lorsque vous voudrez peut-être découpler la durée de vie d'un conteneur de la durée de vie des données contenues.


Assez drôle la réponse que vous avez acceptée est exactement ce que j'ai signalé dans le commentaire précédent vient de rendre plus complexe ... au lieu d'utiliser un vecteur local et un échange avec le global, il suffit d'utiliser le Global ... Maintenant, c'est différent de ce que vous avez disent maintenant. Le transfert de la propriété et de la gestion de la vie est mieux traité avec (intelligent) des pointeurs (intelligents), gardez le conteneur à travers un pointeur et transmettez la propriété autour de ...


4 Réponses :


16
votes
void f() { 
  char *buf = 0; 
  size_t len = 0; 
  std::vector<char> mybuffer; // exists if and only if there are buf and len exist
  { 
      std::vector<char> data = generateSomeData(); 
      mybuffer.swap(data);  // swap without copy
      buf = &mybuffer[0]; 
      len = mybuffer.size(); 
  } 

  // How can I ensure that 'buf' points to valid data at this point, so that the following 
  // line is okay, without copying the data? 
  processData( buf, len ); 
} 

4 commentaires

Fonctionne très bien! Merci - je n'ai pas réalisé qu'il y a une fonction de membre () sur les vecteurs.


@Frerich: Les conteneurs STL comportent généralement un membre membre membre ou un Swap fonction libre. Ce n'est pas une exigence, c'est juste un bon design.


Si vous allez fournir un vecteur global (périmètre extérieur), pourquoi ne pas simplement générer les données dans ce conteneur? {mybuffer = generatesomedata (); } ??


@David Rodríguez - Dribesas: le code qui appelle generatesomedata n'a pas d'accès direct à l'objet mybuffer . Si c'était aussi facile, je n'aurais pas besoin de détacher les données en premier lieu. J'ai écrit que dans ma réponse à votre commentaire à ma question déjà.



-7
votes

Je ne le recommanderais pas, mais: xxx

voila!

Ce code est vraiment sûr car Vec's Destructor appelle Suppr [] 0 ; , qui est une opération sûre (sauf si vous avez une étrange implémentation de STL).


18 commentaires

Vous avez une étrange définition de «sûr».


Je ne suis pas un avocat de langue, mais il faut que ce soit un comportement indéfini là-bas.


-1. Tous les compilateurs décents doivent produire un avertissement sur la ligne MEMSET () .


@ Dummy00001: Eh bien, ils ne le font pas, cela fonctionne, et c'est probablement le seul moyen de détacher le tableau du vecteur.


Oh mon Dieu ... tuer avec un feu!


Supprimer [] PTR; est certainement faux, car par défaut vecteur utilise :: opérateur nouveau et placement neuf (via std :: Allocator ) Pour allouer séparément de la mémoire et créer des objets. Et le MEMSET est un comportement indéfini, même s'il arrive à avoir le bon effet sur votre implémentation.


@ Dummy00001: Malheureusement, toute plainte d'un compilateur sera réduite au silence par le casting vers Void * , mais qui ne supprime pas le fait que le code invoque UB.


@Mike Seymour: Un char n'a pas de destructeur, donc peu importe s'il utilise le placement nouveau. J'ai déjà souligné que je ne le recommande pas et que cela dépend de la mise en œuvre de la STL.


L'acte d'afficher le code et l'appelant Safe ressemble à une recommandation, même si vous Dites Ce n'est pas le cas.


@Viktor: Le placement nouveau n'est pas le problème. Alloué avec de nouveaux et trafiquant avec Supprimer [] est. Même pour la pod, c'est dangereux.


@Dennis: le vecteur alloue et traçable en interne avec [] car il ne connaît pas la taille de la matrice au moment de la compilation.


@Rob Kennedy: Ou vous pouvez le voir comme solution qui répond à la question, mais n'est pas recommandé de faire. Exactement comme je le dis.


+1 pour la création, la pensée pratique et me donner un bon rire.


@Viktor: Non, le vecteur alloue et traçable en interne avec :: opérateur nouveau () , sauf si vous lui donnez un allocator personnalisé qui utilise Nouveau [] .


Et le memset ne définit pas nécessairement le (s) pointeur interne (s) sur NULL; La représentation de Null n'est pas nécessairement nulle.


@Mike Seymour: "Et le memset ne doit pas nécessairement définir le (s) pointeur (s) interne (s) à NULL; la représentation de NULL n'est pas nécessairement". Oui, il y a beaucoup de raisons pour lesquelles j'ai dit "assez sûr" au lieu de "sûr" et a commencé par dire que je ne le recommande pas.


@Mike Seymour: "Le vecteur alloue et traçable en interne avec :: opérateur neuf ()", ok, je pensais que l'alloueur par défaut utilisait de nouveau [] profondément bas quelque part.


-1 Pour continuer à soutenir ce code invalide, présentant au moins deux cas de comportement non défini, est "assez sûr".



2
votes

La solution la plus simple n'a pas encore été encore présentée:

void f() { 
   std::vector<char> data = generateSomeData(); 

  processData( &data[0], data.size() ); 
} 


10 commentaires

-1: Cela répond à une question, mais pas celui que j'ai demandé; I intentionnellement mettre une étendue autour du vecteur de données afin qu'il sorte de portée tôt pour démontrer mon point.


@Frerich: Mais peut-être qu'elle répond à celui que vous auriez dû demander.


@Mike: N'hésitez pas à répondre à "sous le grand pommier dans le jardin", juste au cas où je voulais demander "Où puis-je trouver une ombre pendant les journées chaudes?". : -}


@Frerich: Prendre que vous avez accepté une solution que nécessite ajouter un vecteur dans la portée extérieure, cette solution est la simple version élégante de celle-ci ... Pourquoi voulez-vous créer un deuxième vecteur et Swap si vous pouvez placer le vecteur dans la portée extérieure? Ma prise est que vous n'avez pas demandé ce que vous vouliez savoir, et un détail dans la réponse acceptée vous a aidé à une solution à la question que vous n'avez pas publiée.


@David Rodríguez - Dribesas: J'ai demandé comment détacher un vecteur des données qu'il contient. Je n'ai aucun problème à créer un secondaire Vecteur (avec une durée de vie éventuellement plus longue!) Pour cela. C'est précisément la question qui a répondu Alexey.


@Frerich: Désolé, mais je ne vois toujours pas ton point. Vous avez un vecteur qui sort trop tôt, vous transférez ainsi son contenu sur un vecteur de vie plus long. Quel est le problème avec l'utilisation du vecteur de vie plus long pour stocker les données en premier lieu?


@Bart van Ingen Schenau: Je ne peux pas changer la fonction generatesomeatata , ce n'est pas sous mon contrôle. Il génère toujours un vecteur sur la pile et le transmet vers moi. Maintenant, j'ai besoin de transmettre les données dans le vecteur à un fil différent et que le fil appelle une API hérité c (pas non sous mon contrôle). J'ai donc besoin de vous assurer que les données du vecteur habitent suffisamment longtemps, jusqu'à ce que l'autre thread (et la fonction Legacy C) a terminé leur travail. Cependant, je veux éviter Copier les données.


@Bart van Ingen Schenau: Ma solution est maintenant à faire un nouveau std :: vecteur puis utilisez la fonction swap () pour échanger les données du vecteur retourné par generatesomeomeata avec mon vecteur alloué. Cela garantit que les données vivent jusqu'à ce que le vecteur nouveau 'soit supprimé. L'autre thread supprime le vecteur après avoir appelé la fonction API héritée.


@Frerich: le bit de communiquer les données à un autre fil (au lieu d'une portée extérieure) manquait de votre question. Ma solution ne fonctionne effectivement pas pour l'affaire à travers les threads.


@Bart van Ingen Schenau: Oui, True - Je suppose que j'aurais dû fournir un peu plus de contexte pour éviter cette confusion (j'avais fondamentalement le même argument avec "David Rodríguez - Dribesas").



1
votes

Je donne une réponse à la question initiale ci-dessous. Cependant, gardez à l'esprit que dans la plupart des cas à l'aide de vecteur :: data () code> et réorganisant le code de sorte que le vecteur mentionné ne sort pas de la portée, à l'aide d'un vecteur différent initialisé avec vecteur ( STD :: Move (StreverVector)) CODE> ou Swapping Contenu avec un autre vecteur ( Vector :: Swap (StreverVector) code>) est la meilleure option comme indiqué dans d'autres réponses.

Néanmoins Il existe un moyen de détacher les données d'un vecteur à l'aide d'un allocator personnalisé. Les éléments suivants implémentent l'allocator noalloc code> qui ne fait absolument rien. Il n'allocie pas / ne construit ni ne gêne ni négocie les choses. De plus, il ne contient aucune variables non statiques ou virtuelles. Donc, cela ne devrait pas changer le contenu d'une instance de vecteur code>. Intérieur Détachdata ​​Code> Nous jetons un vecteur ordinaire à un vecteur utilisant le type d'allocator susmentionné en réinterprétant son type et déplacez son contenu sur un nouveau vecteur avec le même allocateur. Lorsque le nouveau vecteur fonctionne hors de portée, il essaiera de détruire et de brancher son contenu. Cependant, comme nous avons annulé cette fonctionnalité dans notre allocator personnalisé cela ne fait absolument rien. La fonction renvoie un pointeur sur les données nouvellement détachées du vecteur d'origine (qui contient maintenant une nullptr sous forme de données et d'éléments zéro). N'oubliez pas de détruire et de négocier les éléments de la matrice manuellement après! P>

#include <iostream>
#include <vector>
#include <new>

// Our custom allocator
template <class T>
struct Noalloc {
    typedef T value_type;
    Noalloc() = default;
    template <class U> constexpr Noalloc(const Noalloc<U>&) noexcept {}
    T* allocate(std::size_t n)
    {
        return nullptr;
    }
    void deallocate(T* p, std::size_t) noexcept {}
    template<class... Args> void construct(T* p, Args&&... args) {}
    void destroy(T* p) {}
};
template <class T, class U>
bool operator==(const Noalloc<T>&, const Noalloc<U>&) { return true; }
template <class T, class U>
bool operator!=(const Noalloc<T>&, const Noalloc<U>&) { return false; }

// detach the data from a vector and return its pointer
template<typename T>
    T* detachData(std::vector<T>& vec)
{
    T* dataPtr = vec.data();
    // swap contents with a new vector
    // that uses "Noalloc" as allocator but is otherwise identical
    std::vector<T, Noalloc<T>> detachHelper
        (std::move(*reinterpret_cast<std::vector<T, Noalloc<T>>*>(&vec)));
    return dataPtr;
    // "detachHelper" runs out of scope here
    // But it will neither destruct nor deallocate
    // given data due to our custom allocator
}

// destroy and deallocate the data manually
template<typename T>
    void destroyData(T* data, size_t n, size_t capacity)
{
    // should be pretty self explanatory...
    std::allocator<T> alloc;
    for(size_t i = 0; i < n; i++)
    {
        std::allocator_traits<std::allocator<T>>::destroy(alloc, data+i);
    }
    std::allocator_traits<std::allocator<T>>::deallocate(alloc, data, capacity);
}

int main()
{
    int* testData = nullptr;
    size_t size = 0;
    // For vectors data size and actually allocated size
    // may differ. So we need to keep track of both.
    // If you want to avoid this you may call "vector::shrink_to_fit()"
    // This should resize the vector to fit its data.
    // However the implementation is not required to fulfill this request.
    // So beware!
    size_t capacity = 0;
    {
        std::vector<int> test{1,2,3,4};
        // copy size and data pointer
        size = test.size();
        capacity = test.capacity();
        testData = detachData(test);
        // "test" runs out of scope here.
        // However after calling "detachData(test)"
        // it doesn't contain any data
    }
    // Print the contents of the array
    for(size_t i = 0; i < size; i++)
    {
        std::cout << testData[i] << std::endl;
    }
    // Don't forget to destroy the data yourself!
    // The original vector doesn't exist anymore
    destroyData(testData, size, capacity);
    std::cout << "data destroyed successfully" << std::endl;
}


1 commentaires

Le * réinterpret_cast provoque un comportement non défini par une violation aloritable stricte