Pourquoi ne pouvons-nous pas changer le pointeur de données de std :: vector?

Dec 30 2020

J'ai un tableau char* sourceet un vecteur std::vector<char> target. Je voudrais faire targetpointer le vecteur sourcedans O (1), sans copier les données.

Quelque chose dans ce sens:

#include <vector>

char* source = new char[3] { 1, 2, 3 };
std::vector<char> target;
target.resize(3);

target.setData(source); // <- Doesn't exist
// OR
std::swap(target.data(), source); // <- swap() does not support char*
delete[] source;

Pourquoi n'est-il pas possible de modifier manuellement l'emplacement vers lequel pointe un vecteur? Y a-t-il un problème spécifique et ingérable qui se poserait si cela était possible?

Réponses

7 anatolyg Dec 30 2020 at 17:39

La vectorclasse C ++ prend en charge l'ajout et la suppression d'éléments, avec un ordre consécutif garanti en mémoire. Si vous pouviez initialiser votre vectoravec la mémoire tampon existante et y ajouter suffisamment d'éléments, cela déborderait ou nécessiterait une réallocation.

L'interface de vectorsuppose qu'elle gère sa mémoire tampon interne, c'est-à-dire qu'elle peut l'allouer, la désallouer, la redimensionner quand elle le souhaite (dans les spécifications, bien sûr). Si vous avez besoin de quelque chose qui n'est pas autorisé à gérer sa mémoire tampon, vous ne pouvez pas utiliser vector- utilisez une structure de données différente ou écrivez-en une vous-même.

Vous pouvez créer un vectorobjet en copiant vos données (en utilisant un constructeur avec deux pointeurs ou assign), mais ce n'est évidemment pas ce que vous voulez.

Alternativement, vous pouvez utiliser string_view, qui ressemble presque ou peut-être exactement ce dont vous avez besoin.

3 NutCracker Dec 30 2020 at 19:23

std::vectorest considéré comme le propriétaire du tampon sous-jacent. Vous pouvez changer le tampon mais ce changement provoque une allocation c'est-à-dire faire une copie du tampon source dont vous ne voulez pas (comme indiqué dans la question).

Vous pouvez faire ce qui suit:


#include <vector>

int main() {
    char* source = new char[3] { 1, 2, 3 };
    std::vector<char> target;
    target.resize(3);
    target.assign(source, source + 3);
    delete[] source;
    return 0;
}

mais encore une fois std::vector::assign:

Remplace le contenu par des copies de ceux de la plage [premier, dernier).

La copie est donc à nouveau exécutée. Vous ne pouvez pas vous en éloigner pendant l'utilisation std::vector.

Si vous ne voulez pas copier de données, vous devez utiliser à std::spanpartir de C ++ 20 (ou créer votre propre span) ou use std::string_view(qui vous semble approprié puisque vous avez un tableau de chars).

1ère option: Utilisation std::string_view

Puisque vous êtes limité à C ++ 17, cela std::string_viewpourrait être parfait pour vous. Il construit une vue des 3 premiers caractères du tableau de caractères en commençant par l'élément pointé par source.

#include <iostream>
#include <string_view>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::string_view strv( source, 3 );

    delete[] source;

    return 0;
}

2ème option: Utilisation à std::spanpartir de C ++ 20

std::spanvient de C ++ 20 donc ce n'est peut-être pas le moyen le plus parfait pour vous, mais vous pourriez être intéressé par ce que c'est et comment cela fonctionne. Vous pouvez penser std::spanà une version un peu généralisée de std::string_viewcar il s'agit d'une séquence contiguë d'objets de tout type, pas seulement de caractères. L'utilisation est similaire à celle du std::string_view:

#include <span>
#include <iostream>

int main() {
    char* source = new char[3] { 1, 2, 3 };

    std::span s( source, 3 );

    delete[] source;

    return 0;
}

3ème option: votre propre portée

Si vous êtes limité à C ++ 17, vous pouvez penser à créer votre propre spanstructure. C'est peut-être encore exagéré, mais laissez-moi vous montrer (au fait, jetez un œil à cette réponse plus élaborée ):

template<typename T>
class span {
   T* ptr_;
   std::size_t len_;

public:
    span(T* ptr, std::size_t len) noexcept
        : ptr_{ptr}, len_{len}
    {}

    T& operator[](int i) noexcept {
        return *ptr_[i];
    }

    T const& operator[](int i) const noexcept {
        return *ptr_[i];
    }

    std::size_t size() const noexcept {
        return len_;
    }

    T* begin() noexcept {
        return ptr_;
    }

    T* end() noexcept {
        return ptr_ + len_;
    }
};

int main() {
    char* source = new char[3] { 1, 2, 3 };

    span s( source, 3 );

    delete[] source;

    return 0;
}

L'utilisation est donc la même que celle de la version C ++ 20 de std::span.