O std :: vector está copiando os objetos com um push_back?

169

Depois de muitas investigações com o valgrind, concluí que std :: vector faz uma cópia de um objeto que você deseja enviar push_back.

Isso é mesmo verdade? Um vetor não pode manter uma referência ou um ponteiro de um objeto sem uma cópia ?!

obrigado

Benlaug
fonte
20
Este é um princípio básico do C ++. Objetos são valores. A atribuição faz uma cópia. Duas variáveis ​​referentes ao mesmo objeto não são possíveis, a menos que você modifique o tipo com *ou &para fazer um ponteiro ou referência.
21118 Daniel Earwicker
8
@DanielEarwicker push_back realmente leva uma referência. Não está claro apenas pela assinatura que ele fará uma cópia.
Brian Gordon
3
@BrianGordon - Não estou dizendo que sim! Daí a necessidade do princípio norteador. Mesmo assim, podemos deduzir algo da assinatura de push_back: leva a const&. Ou joga fora o valor (inútil) ou existe um método de recuperação. Então, olhamos para a assinatura de back, e ela retorna clara &, de modo que o valor original foi copiado ou constfoi descartado silenciosamente (muito ruim: comportamento potencialmente indefinido). Assim, assumindo que os projetistas vectoreram racionais ( vector<bool>não obstante), concluímos que ele faz cópias.
Daniel Earwicker

Respostas:

183

Sim, std::vector<T>::push_back()cria uma cópia do argumento e a armazena no vetor. Se você deseja armazenar ponteiros para objetos em seu vetor, crie um em std::vector<whatever*>vez de std::vector<whatever>.

No entanto, você precisa garantir que os objetos referenciados pelos ponteiros permaneçam válidos enquanto o vetor mantém uma referência a eles (ponteiros inteligentes utilizando o idioma RAII resolvem o problema).

Alexander Gessler
fonte
Eu também observaria que, se você usa ponteiros brutos, agora é responsável por limpar depois deles. Não há um bom motivo para fazer isso (nenhum que eu possa imaginar), você deve sempre usar um ponteiro inteligente.
Ed S.
1
Dito isso, você não deve usar std :: auto_ptr em recipientes STL, para mais informações: -porque-é-errado de usar-stdauto-PTR-com-contentores normais
OriginalCliche
24
Desde o C ++ 11, push_backexecutará uma movimentação em vez de uma cópia se o argumento for uma referência de rvalue. (Os objectos podem ser convertidas em referências rvalue com std::move().)
emlai
2
@tuple_cat, seu comentário deve dizer "se o argumento for um rvalue". (Se o argumento é o nome de uma entidade declarada como referência rvalue, então o argumento é realmente um lvalue e não será movido) - verifique minha edição na resposta de "Karl Nicoll" que cometeu esse erro inicialmente
MM
Há uma resposta abaixo, mas para esclarecer: Como o C ++ 11 também use emplace_backpara evitar qualquer cópia ou movimentação (objeto de construção no local fornecido pelo contêiner).
Wormer
34

Sim, std::vectorarmazena cópias. Como devevector saber quais são os tempos de vida esperados dos seus objetos?

Se você deseja transferir ou compartilhar a propriedade dos objetos, use ponteiros, possivelmente ponteiros inteligentes como shared_ptr(encontrados no Boost ou TR1 ) para facilitar o gerenciamento de recursos.

Georg Fritzsche
fonte
3
Aprenda a usar o shared_ptr - eles fazem exatamente o que você deseja. Meu idioma favorito é typedef boost :: shared_ptr <Foo> FooPtr; Em seguida, faça recipientes de FooPtrs
pm100
3
@ pm100 - Você sabe boost::ptr_vector?
Manuel
2
Eu também gosto de usar class Foo { typedef boost::shared_ptr<Foo> ptr; };apenas para escrever Foo::ptr.
Rupert Jones
2
@ pm100 - shared_ptrnão é exatamente fogo e esqueça. Veja stackoverflow.com/questions/327573 e stackoverflow.com/questions/701456
Daniel Earwicker
2
shared_ptr é bom se você possui propriedade compartilhada, mas geralmente é usado em excesso. unique_ptr ou boost scoped_ptr fazem muito mais sentido quando a propriedade é clara.
Nemanja Trifunovic
28

A partir do C ++ 11, todos os contêineres padrão ( std::vector, std::mapetc) oferecem suporte à semântica de movimentação, o que significa que agora você pode passar rvalues ​​para contêineres padrão e evitar uma cópia:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

Como alternativa, você pode usar vários ponteiros inteligentes para obter o mesmo efeito:

std::unique_ptr exemplo

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr exemplo

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.
Karl Nicoll
fonte
2
Observe que std::make_unique(irritantemente) está disponível apenas no C ++ 14 e acima. Diga ao seu compilador para definir sua conformidade padrão de acordo, se desejar compilar esses exemplos.
Laryx Decidua
Em 5a você pode usar auto pFoo =para evitar repetições; e todos os std::stringmoldes podem ser removidos (não há conversão implícita de literais para std::string)
MM
2
@ user465139 make_uniquepode ser facilmente implementado em C ++ 11, por isso é apenas uma ligeira irritação para alguém preso com um C ++ 11 compilador
MM
1
@ MM: De fato. Aqui é a implementação livro:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decídua
1
@ Anakin - Sim, eles devem fazer, mas apenas se você copiar. Se você usar std::move()com std::shared_ptr, o ponteiro compartilhado original pode ter seu ponteiro alterado desde que a propriedade foi passada para o vetor. Veja aqui: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll
15

O std :: vector sempre faz uma cópia do que estiver sendo armazenado no vetor.

Se você estiver mantendo um vetor de ponteiros, ele fará uma cópia do ponteiro, mas não a instância para a qual o ponteiro está apontando. Se você estiver lidando com objetos grandes, poderá (e provavelmente deve) sempre usar um vetor de ponteiros. Freqüentemente, o uso de um vetor de ponteiros inteligentes de um tipo apropriado é bom para fins de segurança, pois o manuseio da vida útil do objeto e o gerenciamento de memória podem ser complicados.

Reed Copsey
fonte
3
não depende do tipo. Sempre faz uma cópia. Se for um ponteiro, faça uma cópia do ponteiro
pm100 16/02/2010
Vocês dois estão certos. Tecnicamente, sim, ele sempre faz uma cópia. Praticamente, se você passar um ponteiro para o objeto, ele copia o ponteiro, não o objeto. Com segurança, você deve usar um ponteiro inteligente apropriado.
Steven Sudit 16/02/10
1
Sim, está sempre copiando - No entanto, o "objeto" ao qual o OP está se referindo é provavelmente uma classe ou estrutura, então eu estava me referindo se a cópia do "Objeto" depende da definição. Mal escrito, no entanto.
Reed Copsey
3

O std :: vector não apenas faz uma cópia do que você está empurrando, mas a definição da coleção afirma que isso será feito e que você não poderá usar objetos sem a semântica correta da cópia em um vetor. Portanto, por exemplo, você não usa auto_ptr em um vetor.

Liz Albin
fonte
2

Relevante no C ++ 11 é a emplacefamília de funções-membro, que permitem transferir a propriedade de objetos, movendo-os para contêineres.

O idioma de uso seria semelhante

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

A movimentação para o objeto lvalue é importante, caso contrário, ela seria encaminhada como uma referência ou referência const e o construtor da movimentação não seria chamado.

LemonPi
fonte
0

se você não quiser as cópias; então a melhor maneira é usar um vetor de ponteiro (ou outra estrutura que sirva para o mesmo objetivo). se você quiser as cópias; use diretamente push_back (). você não tem outra escolha.

rahmivolkan
fonte
1
Uma observação sobre os vetores de ponteiro: o vetor <shared_ptr <obj>> é muito mais seguro que o vetor <obj *> e shared_ptr faz parte do padrão no ano passado.
rich.e
-1

Por que demorou bastante investigação para descobrir isso! Apenas prove com um código simples, por exemplo

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

Se "olá mundo" for impresso, o objeto deverá ter sido copiado

NexusSquared
fonte
4
Isso não constitui uma prova. Se o objeto não fosse copiado, sua última declaração seria um comportamento indefinido e poderia imprimir um alô.
Tapete de
4
o teste correto seria modificar um dos dois após a inserção. Se eles fossem o mesmo objeto (se o vetor armazenasse uma referência), ambos seriam modificados.
Francesco Dondi 12/02