Por que não podemos alterar o ponteiro de dados de std :: vector?

Dec 30 2020

Eu tenho uma matriz char* sourcee um vetor std::vector<char> target. Eu gostaria de fazer o vetor targetapontar para sourceem O (1), sem copiar os dados.

Algo nesse sentido:

#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;

Por que não é possível alterar manualmente para onde um vetor aponta? Existe algum problema específico e incontrolável que surgiria se isso fosse possível?

Respostas

7 anatolyg Dec 30 2020 at 17:39

A vectorclasse C ++ oferece suporte à adição e exclusão de elementos, com ordem consecutiva garantida na memória. Se você pudesse inicializar seu vectorcom o buffer de memória existente e adicionar elementos suficientes a ele, ele estouraria ou exigiria realocação.

A interface do vectorassume que gerencia seu buffer interno, ou seja, pode alocar, desalocar, redimensionar quando quiser (dentro das especificações, é claro). Se você precisa de algo que não tem permissão para gerenciar seu buffer, você não pode usar vector- use uma estrutura de dados diferente ou escreva uma você mesmo.

Você pode criar um vectorobjeto copiando seus dados (usando um construtor com dois ponteiros ou assign), mas obviamente não é isso que você deseja.

Como alternativa, você pode usar o string_view, que parece quase ou talvez exatamente o que você precisa.

3 NutCracker Dec 30 2020 at 19:23

std::vectoré considerado o proprietário do buffer subjacente. Você pode alterar o buffer, mas essa alteração causa alocação, ou seja, fazer uma cópia do buffer de origem que você não deseja (conforme declarado na pergunta).

Você pode fazer o seguinte:


#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;
}

mas novamente std::vector::assign:

Substitui o conteúdo por cópias daqueles no intervalo [primeiro, último).

Portanto, a cópia é executada novamente. Você não pode fugir dele durante o uso std::vector.

Se não quiser copiar dados, você deve usar std::spando C ++ 20 (ou criar seu próprio intervalo) ou usar std::string_view(que parece adequado para você, pois você tem uma matriz de chars).

1ª opção: usando std::string_view

Como você está limitado a C ++ 17, std::string_viewpode ser perfeito para você. Ele constrói uma visão dos primeiros 3 caracteres da matriz de caracteres, começando com o elemento apontado por 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ª opção: usando a std::spanpartir de C ++ 20

std::spanvem do C ++ 20, então pode não ser a maneira mais perfeita para você, mas você pode estar interessado em saber o que é e como funciona. Você pode pensar nisso std::spancomo uma versão um pouco generalizada de std::string_viewporque é uma sequência contígua de objetos de qualquer tipo, não apenas caracteres. O uso é semelhante ao de 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ª opção: seu próprio período

Se você está limitado a C ++ 17, pode pensar em criar sua própria spanestrutura. Ainda pode ser um exagero, mas deixe-me mostrar a você (a propósito, dê uma olhada nesta resposta mais elaborada ):

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;
}

Portanto, o uso é o mesmo da versão C ++ 20 do std::span.