Devo armazenar objetos inteiros ou ponteiros para objetos em contêineres?

162

Projetando um novo sistema a partir do zero. Vou usar o STL para armazenar listas e mapas de certos objetos de vida longa.

Pergunta: Devo garantir que meus objetos tenham construtores de cópias e armazenar cópias de objetos em meus contêineres STL, ou é geralmente melhor gerenciar a vida e o escopo sozinho e apenas armazenar os ponteiros para esses objetos nos contêineres STL?

Sei que isso é um pouco curto nos detalhes, mas estou procurando a melhor resposta "teórica", se existir, pois sei que essas duas soluções são possíveis.

Duas desvantagens muito óbvias de brincar com ponteiros: 1) Devo gerenciar a alocação / desalocação desses objetos em um escopo além do STL. 2) Não consigo criar um objeto temporário na pilha e adicioná-lo aos meus contêineres.

Falta mais alguma coisa?

Stéphane
fonte
36
Deus eu amo este site, esta é a pergunta exata que eu estava pensando em hoje ... obrigado por fazer o trabalho de perguntar para mim :-)
eviljack
2
Outra coisa interessante é que devemos verificar se o ponteiro foi realmente adicionado à coleção e, se não o fizer, provavelmente chamaremos delete para evitar vazamentos de memória ... if ((set.insert (pointer)). second = false) {delete pointer;}
javapowered 11/03/11

Respostas:

68

Desde que as pessoas estão compartilhando a eficácia do uso de ponteiros.

Se você estiver pensando em usar um std :: vector e se houver poucas atualizações e você repetir sua coleção, é um objeto de armazenamento de tipo não polimórfico, "cópias" serão mais eficazes, pois você obterá uma melhor localidade de referência.

Otoh, se as atualizações forem comuns, o armazenamento de ponteiros economizará os custos de cópia / realocação.

Torbjörn Gyllebring
fonte
7
Em termos de localidade do cache, o armazenamento de ponteiros em vetor pode ser eficiente se usado junto com um alocador personalizado para os apontadores. O alocador personalizado precisa cuidar da localidade do cache, por exemplo, usando o posicionamento new (consulte en.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
27711
47

Isso realmente depende da sua situação.

Se seus objetos são pequenos e fazer uma cópia do objeto é leve, então armazenar os dados em um contêiner stl é simples e fácil de gerenciar, na minha opinião, porque você não precisa se preocupar com o gerenciamento da vida útil.

Se seus objetos são grandes e ter um construtor padrão não faz sentido, ou cópias de objetos são caras, o armazenamento com ponteiros é provavelmente o caminho a percorrer.

Se você decidir usar ponteiros para objetos, dê uma olhada na Biblioteca de Contêineres de Boost Pointer . Essa biblioteca de reforço agrupa todos os contêineres STL para uso com objetos alocados dinamicamente.

Cada contêiner de ponteiro (por exemplo, ptr_vector) assume a propriedade de um objeto quando ele é adicionado ao contêiner e gerencia a vida útil desses objetos para você. Você também acessa todos os elementos em um contêiner ptr_ por referência. Isso permite que você faça coisas como

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Essas classes envolvem os contêineres STL e funcionam com todos os algoritmos STL, o que é realmente útil.

Também existem facilidades para transferir a propriedade de um ponteiro no contêiner para o chamador (através da função de liberação na maioria dos contêineres).

Nick Haddad
fonte
38

Se você estiver armazenando objetos polimórficos, sempre precisará usar uma coleção de ponteiros de classe base.

Ou seja, se você planeja armazenar diferentes tipos derivados em sua coleção, deve armazenar ponteiros ou ser comido pelo deamon fatiado.

Torbjörn Gyllebring
fonte
1
Eu amei o deamon fatiado!
precisa saber é
22

Desculpe pular três anos após o evento, mas uma nota de advertência aqui ...

No meu último grande projeto, minha estrutura central de dados era um conjunto de objetos bastante diretos. Cerca de um ano após o projeto, à medida que os requisitos evoluíram, percebi que o objeto realmente precisava ser polimórfico. Foram necessárias algumas semanas de cirurgia cerebral difícil e desagradável para corrigir a estrutura de dados para ser um conjunto de indicadores de classe base e para lidar com todos os danos colaterais no armazenamento de objetos, na projeção e assim por diante. Levei alguns meses para me convencer de que o novo código estava funcionando. Aliás, isso me fez pensar muito sobre o quão bem projetado é o modelo de objeto do C ++.

No meu grande projeto atual, minha estrutura central de dados é um conjunto de objetos bastante diretos. Cerca de um ano depois do projeto (o que acontece hoje), percebi que o objeto realmente precisa ser polimórfico. De volta à rede, localizei este segmento e localizei o link de Nick para a biblioteca de contêineres de ponteiro Boost. Isso é exatamente o que eu tive que escrever da última vez para consertar tudo, então vou tentar desta vez.

A moral, para mim, de qualquer maneira: se suas especificações não forem 100% moldadas em pedra, procure dicas e você poderá economizar muito trabalho mais tarde.

EML
fonte
As especificações nunca são gravadas em pedra. Eu não acho que isso significa que você deve usar exclusivamente recipientes de ponteiro, embora os recipientes de ponteiro Boost pareçam tornar essa opção muito mais atraente. Sou cético quanto ao fato de que você precisa revisar todo o programa de uma só vez se decidir que um contêiner de objeto deve ser convertido em um contêiner de ponteiro. Esse pode ser o caso em alguns modelos. Nesse caso, é um design frágil. Nesse caso, não culpe seu problema pela "fraqueza" dos contêineres de objetos.
Allyourcode # 25/13
Você poderia ter deixado o item no vetor com semântica de valores e feito o comportamento polimórfico dentro.
Billy ONeal
19

Por que não obter o melhor dos dois mundos: faça um contêiner de ponteiros inteligentes (como boost::shared_ptrou std::shared_ptr). Você não precisa gerenciar a memória e não precisa lidar com grandes operações de cópia.

Branan
fonte
Qual é a diferença dessa abordagem do que Nick Haddad sugeriu usando a Boost Pointer Container Library?
Thorsten Schöning
10
@ ThorstenSchöning std :: shared_ptr não adiciona uma dependência ao boost.
James Johnston
Você não pode usar ponteiros compartilhada para lidar com o seu polimorfismo para que acabaria por perder esse recurso com esta abordagem, a menos que você converter explicitamente ponteiros
auserdude
11

Geralmente, armazenar os objetos diretamente no contêiner STL é melhor, pois é mais simples, mais eficiente e mais fácil de usar o objeto.

Se o seu objeto tiver uma sintaxe não copiável ou for um tipo de base abstrato, será necessário armazenar ponteiros (o mais fácil é usar o shared_ptr)

Greg Rogers
fonte
4
Não é mais eficiente se seus objetos forem grandes e você mover elementos com frequência.
Allyourcode
3

Você parece ter uma boa noção da diferença. Se os objetos são pequenos e fáceis de copiar, então guarde-os.

Caso contrário, eu pensaria em armazenar ponteiros inteligentes (não auto_ptr, um ref que conta ponteiros inteligentes) para aqueles que você aloca no heap. Obviamente, se você optar por ponteiros inteligentes, não poderá armazenar objetos alocados da pilha temporária (como você disse).

@ Torbjörn faz um bom argumento sobre fatiar.

Lou Franco
fonte
1
Ah, e nunca mais nunca nunca criar uma coleção de auto_ptr de
Torbjörn Gyllebring
Certo, auto_ptr não é um ponteiro inteligente - ele não conta.
Lou Franco
O auto_ptr também não possui semântica de cópia não destrutiva. O ato de atribuição de um auto_ptr from onepara anotherliberará a referência onee será alterada one.
Andy Finkenstadt
2

Se os objetos tiverem que ser referidos em outra parte do código, armazene em um vetor de boost :: shared_ptr. Isso garante que os ponteiros para o objeto permaneçam válidos se você redimensionar o vetor.

Ou seja:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Se ninguém mais armazenar ponteiros para os objetos, ou a lista não aumentar e diminuir, basta armazenar como objetos antigos simples:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

fonte
1

Esta pergunta está me incomodando há um tempo.

Eu gosto de armazenar ponteiros, mas tenho alguns requisitos adicionais (wrappers lua SWIG) que podem não se aplicar a você.

O ponto mais importante neste post é testar você mesmo , usando seus objetos

Fiz isso hoje para testar a velocidade de chamar uma função de membro em uma coleção de 10 milhões de objetos, 500 vezes.

A função atualiza x e y com base em xdir e ydir (todas as variáveis ​​de membro flutuantes).

Eu usei um std :: list para armazenar os dois tipos de objetos e descobri que armazenar o objeto na lista é um pouco mais rápido do que usar um ponteiro. Por outro lado, o desempenho foi muito próximo, portanto, tudo se resume a como eles serão usados ​​no seu aplicativo.

Para referência, com -O3 no meu hardware, os ponteiros levaram 41 segundos para serem concluídos e os objetos brutos levaram 30 segundos para serem concluídos.

Meleneth
fonte