Estou vindo de um plano de fundo Java e comecei a trabalhar com objetos em C ++. Mas uma coisa que me ocorreu é que as pessoas costumam usar ponteiros para objetos em vez dos próprios objetos, por exemplo, esta declaração:
Object *myObject = new Object;
ao invés de:
Object myObject;
Ou, em vez de usar uma função, digamos testFunc()
assim:
myObject.testFunc();
nós temos que escrever:
myObject->testFunc();
Mas não consigo descobrir por que devemos fazer dessa maneira. Eu diria que isso tem a ver com eficiência e velocidade, já que temos acesso direto ao endereço de memória. Estou certo?
Respostas:
É muito lamentável que você veja alocação dinâmica com tanta frequência. Isso apenas mostra quantos programadores C ++ ruins existem.
De certa forma, você tem duas perguntas agrupadas em uma. A primeira é quando devemos usar a alocação dinâmica (usando
new
)? A segunda é quando devemos usar ponteiros?A mensagem importante para levar para casa é que você sempre deve usar a ferramenta apropriada para o trabalho . Em quase todas as situações, há algo mais apropriado e seguro do que realizar alocação dinâmica manual e / ou usar ponteiros brutos.
Alocação dinâmica
Na sua pergunta, você demonstrou duas maneiras de criar um objeto. A principal diferença é a duração do armazenamento do objeto. Ao fazer
Object myObject;
dentro de um bloco, o objeto é criado com duração de armazenamento automático, o que significa que será destruído automaticamente quando ficar fora do escopo. Ao fazê-lonew Object()
, o objeto tem duração de armazenamento dinâmico, o que significa que permanece ativo até você explicitamentedelete
. Você só deve usar a duração do armazenamento dinâmico quando precisar. Ou seja, você sempre deve preferir criar objetos com duração de armazenamento automático quando puder .As duas principais situações nas quais você pode exigir alocação dinâmica:
Quando você precisar absolutamente de alocação dinâmica, encapsule-o em um ponteiro inteligente ou em outro tipo que execute RAII (como os contêineres padrão). Ponteiros inteligentes fornecem semântica de propriedade de objetos alocados dinamicamente. Dê uma olhada
std::unique_ptr
estd::shared_ptr
, por exemplo. Se você usá-los adequadamente, poderá quase inteiramente evitar executar seu próprio gerenciamento de memória (consulte a Regra do Zero ).Ponteiros
No entanto, existem outros usos mais gerais para ponteiros brutos além da alocação dinâmica, mas a maioria possui alternativas que você prefere. Como antes, sempre prefira as alternativas, a menos que você realmente precise de indicadores .
Você precisa de semântica de referência . Às vezes, você deseja passar um objeto usando um ponteiro (independentemente de como foi alocado), porque deseja que a função para a qual você está passando tenha acesso a esse objeto específico (não uma cópia dele). No entanto, na maioria das situações, você deve preferir tipos de referência a ponteiros, porque é especificamente para isso que eles foram projetados. Observe que não se trata necessariamente de estender a vida útil do objeto além do escopo atual, como na situação 1 acima. Como antes, se você concorda em passar uma cópia do objeto, não precisa de semântica de referência.
Você precisa de polimorfismo . Você só pode chamar funções polimorficamente (ou seja, de acordo com o tipo dinâmico de um objeto) através de um ponteiro ou referência ao objeto. Se esse é o comportamento que você precisa, use ponteiros ou referências. Novamente, as referências devem ser preferidas.
Você deseja representar que um objeto é opcional , permitindo que um
nullptr
seja passado quando o objeto estiver sendo omitido. Se for um argumento, você deve preferir usar argumentos padrão ou sobrecargas de função. Caso contrário, você deve preferencialmente usar um tipo que encapsule esse comportamento, comostd::optional
(introduzido no C ++ 17 - com padrões anteriores do C ++, useboost::optional
).Você deseja desacoplar unidades de compilação para melhorar o tempo de compilação . A propriedade útil de um ponteiro é que você só precisa de uma declaração de encaminhamento do tipo apontado (para realmente usar o objeto, você precisará de uma definição). Isso permite desacoplar partes do seu processo de compilação, o que pode melhorar significativamente o tempo de compilação. Veja o idioma Pimpl .
Você precisa interagir com uma biblioteca C ou uma biblioteca de estilo C. Neste ponto, você é forçado a usar ponteiros brutos. A melhor coisa que você pode fazer é garantir que você solte os ponteiros brutos no último momento possível. Você pode obter um ponteiro bruto de um ponteiro inteligente, por exemplo, usando sua
get
função de membro. Se uma biblioteca executar uma alocação para você que espera desalocar por meio de um identificador, muitas vezes você pode agrupar o identificador em um ponteiro inteligente com um deleter personalizado que desalocará o objeto adequadamente.fonte
Object myObject(param1, etc...)
Existem muitos casos de uso para ponteiros.
Comportamento polimórfico . Para tipos polimórficos, ponteiros (ou referências) são usados para evitar o fatiamento:
Semântica de referência e evitando copiar . Para tipos não polimórficos, um ponteiro (ou uma referência) evitará copiar um objeto potencialmente caro
Observe que o C ++ 11 possui semântica de movimentação que pode evitar muitas cópias de objetos caros no argumento de função e como valores de retorno. Mas o uso de um ponteiro definitivamente os evitará e permitirá vários ponteiros no mesmo objeto (enquanto um objeto só pode ser movido uma vez).
Aquisição de recursos . Criar um ponteiro para um recurso usando o
new
operador é um antipadrão no C ++ moderno. Use uma classe de recurso especial (um dos contêineres Padrão) ou um ponteiro inteligente (std::unique_ptr<>
oustd::shared_ptr<>
). Considerar:vs.
Um ponteiro bruto deve ser usado apenas como uma "visualização" e não de forma alguma envolvida na propriedade, seja por meio da criação direta ou implicitamente por meio de valores de retorno. Consulte também estas perguntas e respostas nas perguntas frequentes sobre C ++ .
Controle de vida útil mais refinado Sempre que um ponteiro compartilhado é copiado (por exemplo, como argumento de função), o recurso para o qual ele aponta é mantido vivo. Objetos regulares (não criados por você
new
, diretamente por você ou dentro de uma classe de recurso) são destruídos ao sair do escopo.fonte
unique_ptr
/ move semânticahun(b)
também requer conhecimento da assinatura, a menos que você esteja bem em não saber que forneceu o tipo errado até a compilação. Embora o problema de referência geralmente não seja detectado no momento da compilação e exija mais esforço para depurar, se você estiver verificando a assinatura para garantir que os argumentos estejam corretos, também poderá ver se algum dos argumentos é referência portanto, o bit de referência se torna um problema (principalmente ao usar IDEs ou editores de texto que mostram a assinatura de uma função selecionada). Além dissoconst&
,.Existem muitas respostas excelentes para essa pergunta, incluindo os importantes casos de uso de declarações avançadas, polimorfismo etc., mas sinto que uma parte da "alma" da sua pergunta não foi respondida - ou seja, o que significam as diferentes sintaxes em Java e C ++.
Vamos examinar a situação comparando os dois idiomas:
Java:
O equivalente mais próximo disso é:
C ++:
Vamos ver a maneira alternativa do C ++:
A melhor maneira de pensar é que - mais ou menos - Java (implicitamente) manipula ponteiros para objetos, enquanto o C ++ pode manipular ponteiros para objetos ou os próprios objetos. Há exceções a isso - por exemplo, se você declarar tipos "primitivos" de Java, eles são valores reais que são copiados, e não indicadores. Assim,
Java:
Dito isto, o uso de ponteiros NÃO é necessariamente a maneira correta ou errada de lidar com as coisas; no entanto, outras respostas cobriram isso satisfatoriamente. A idéia geral, porém, é que, em C ++, você tem muito mais controle sobre a vida útil dos objetos e sobre onde eles irão morar.
O ponto principal
Object * object = new Object()
é que a construção é a que mais se aproxima da semântica típica de Java (ou C #).fonte
Object2 is now "dead"
: Eu acho que você quer dizermyObject1
ou mais precisamentethe object pointed to by myObject1
.Object object1 = new Object(); Object object2 = new Object();
é um código muito ruim. O segundo novo ou o segundo construtor de objeto pode ser lançado e agora o objeto1 está vazado. Se você estiver usandonew
s não processados , você devenew
agrupar objetos editados em wrappers RAII o mais rápido possível.Outro bom motivo para usar ponteiros seria para declarações de encaminhamento . Em um projeto grande o suficiente, eles podem realmente acelerar o tempo de compilação.
fonte
std::unique_ptr<T>
trabalha com declarações avançadas deT
. Você só precisa ter certeza de que quando o destruidor dostd::unique_ptr<T>
é chamado,T
é um tipo completo. Isso normalmente significa que sua classe que contém ostd::unique_ptr<T>
declara seu destruidor no arquivo de cabeçalho e a implementa no arquivo cpp (mesmo que a implementação esteja vazia).Prefácio
Java não é nada como C ++, ao contrário do hype. A máquina de hype do Java gostaria que você acreditasse que, como o Java possui sintaxe semelhante ao C ++, as linguagens são semelhantes. Nada pode estar mais longe da verdade. Essa desinformação é parte do motivo pelo qual os programadores Java acessam o C ++ e usam sintaxe do tipo Java sem entender as implicações de seu código.
Em diante vamos
Pelo contrário, na verdade. A pilha é muito mais lenta que a pilha, porque a pilha é muito simples em comparação com a pilha. As variáveis de armazenamento automático (também conhecidas como variáveis de pilha) têm seus destruidores chamados quando saem do escopo. Por exemplo:
Por outro lado, se você usar um ponteiro alocado dinamicamente, seu destruidor deverá ser chamado manualmente.
delete
chama esse destruidor para você.Isso não tem nada a ver com a
new
sintaxe predominante em C # e Java. Eles são usados para finalidades completamente diferentes.Benefícios da alocação dinâmica
Um dos primeiros problemas com os quais muitos programadores de C ++ se deparam é que, quando aceitam entrada arbitrária dos usuários, você só pode alocar um tamanho fixo para uma variável de pilha. Você também não pode alterar o tamanho das matrizes. Por exemplo:
Obviamente, se você usou um
std::string
,std::string
redimensiona-se internamente para que não seja um problema. Mas, essencialmente, a solução para esse problema é a alocação dinâmica. Você pode alocar memória dinâmica com base na entrada do usuário, por exemplo:Como o heap é muito maior que a pilha, é possível alocar / realocar arbitrariamente a quantidade de memória necessária, enquanto a pilha tem uma limitação.
Como isso é um benefício que você pergunta? A resposta ficará clara quando você entender a confusão / mito por trás de matrizes e ponteiros. É comum presumir que eles são iguais, mas não são. Esse mito deriva do fato de que os ponteiros podem ser subscritos da mesma forma que matrizes e, devido a matrizes decaírem para ponteiros no nível superior em uma declaração de função. No entanto, uma vez que uma matriz decai para um ponteiro, o ponteiro perde sua
sizeof
informações. Assimsizeof(pointer)
, fornecerá o tamanho do ponteiro em bytes, que geralmente é de 8 bytes em um sistema de 64 bits.Você não pode atribuir a matrizes, apenas inicializá-las. Por exemplo:
Por outro lado, você pode fazer o que quiser com ponteiros. Infelizmente, como a distinção entre ponteiros e matrizes é feita manualmente em Java e C #, os iniciantes não entendem a diferença.
Java e C # têm recursos que permitem tratar objetos como outro, por exemplo, usando a
as
palavra - chave. Portanto, se alguém quisesse tratar umEntity
objeto como umPlayer
objeto,Player player = Entity as Player;
seria possível. Isso é muito útil se você pretende chamar funções em um contêiner homogêneo que deve ser aplicado apenas a um tipo específico. A funcionalidade pode ser alcançada de maneira semelhante abaixo:Por exemplo, digamos que se apenas Triângulos tivesse uma função Girar, seria um erro do compilador se você tentasse chamá-la em todos os objetos da classe. Usando
dynamic_cast
, você pode simular oas
palavra chave. Para ficar claro, se uma conversão falhar, ele retornará um ponteiro inválido. Portanto,!test
é essencialmente um atalho para verificar setest
é NULL ou um ponteiro inválido, o que significa que a conversão falhou.Benefícios das variáveis automáticas
Depois de ver todas as grandes coisas que a alocação dinâmica pode fazer, você provavelmente está se perguntando por que alguém NÃO usaria alocação dinâmica o tempo todo? Eu já te disse uma razão, a pilha é lenta. E se você não precisa de toda essa memória, não deve abusar dela. Então, aqui estão algumas desvantagens em nenhuma ordem específica:
É propenso a erros. A alocação manual de memória é perigosa e você é propenso a vazamentos. Se você não for proficiente no uso do depurador ou
valgrind
(uma ferramenta de vazamento de memória), poderá arrancar o cabelo da cabeça. Felizmente, expressões idiomáticas da RAII e indicadores inteligentes aliviam um pouco isso, mas você deve estar familiarizado com práticas como A Regra dos Três e a Regra dos Cinco. É muita informação, e iniciantes que não sabem ou não se importam cairão nessa armadilha.Não é necessário. Ao contrário de Java e C #, onde é idiomático usar a
new
palavra - chave em qualquer lugar, em C ++, você deve usá-la somente se precisar. A frase comum diz: tudo parece um prego se você tiver um martelo. Enquanto os iniciantes que começam com C ++ têm medo de ponteiros e aprendem a usar variáveis de pilha por hábito, os programadores de Java e C # começam usando ponteiros sem entendê-los! Isso está literalmente saindo com o pé errado. Você deve abandonar tudo o que sabe, porque a sintaxe é uma coisa, aprender o idioma é outra.Uma otimização que muitos compiladores fazem são coisas chamadas elision e otimização de valor de retorno . Essas coisas podem impedir cópias desnecessárias, úteis para objetos muito grandes, como um vetor que contém muitos elementos. Normalmente, a prática comum é usar ponteiros para transferir a propriedade, em vez de copiar os objetos grandes para movê- los. Isso levou à criação de semânticas de movimento e ponteiros inteligentes .
Se você estiver usando ponteiros, (N) RVO NÃO ocorre. É mais benéfico e menos propenso a erros tirar proveito do (N) RVO em vez de retornar ou passar ponteiros se você estiver preocupado com a otimização. Vazamentos de erro podem ocorrer se o chamador de uma função for responsável por
delete
um objeto alocado dinamicamente e tal. Pode ser difícil rastrear a propriedade de um objeto se ponteiros estiverem sendo passados como uma batata quente. Basta usar variáveis de pilha porque é mais simples e melhor.fonte
{ std::string* s = new std::string; } delete s; // destructor called
.... certamente issodelete
não funcionará porque o compilador não saberá mais o ques
é?O C ++ fornece três maneiras de passar um objeto: por ponteiro, por referência e por valor. Java limita você com o último (a única exceção são tipos primitivos como int, booleano etc.). Se você deseja usar o C ++ não apenas como um brinquedo estranho, é melhor conhecer a diferença entre essas três maneiras.
Java finge que não há nenhum problema como 'quem e quando deve destruir isso?'. A resposta é: O Coletor de Lixo, Ótimo e Horrível. No entanto, ele não pode fornecer 100% de proteção contra vazamentos de memória (sim, java pode vazar memória ). Na verdade, o GC fornece uma falsa sensação de segurança. Quanto maior o seu SUV, maior o seu caminho para o evacuador.
O C ++ o deixa frente a frente com o gerenciamento do ciclo de vida do objeto. Bem, existem meios de lidar com isso ( família de ponteiros inteligentes , QObject no Qt e assim por diante), mas nenhum deles pode ser usado da maneira "atire e esqueça" como o GC: você deve sempre ter em mente o manuseio da memória. Não apenas você deve se preocupar em destruir um objeto, mas também deve evitar destruir o mesmo objeto mais de uma vez.
Ainda não está assustado? Ok: referências cíclicas - lide com você mesmo, humano. E lembre-se: mate cada objeto precisamente uma vez; nós, tempos de execução C ++, não gostamos daqueles que mexem com cadáveres, deixam os mortos em paz.
Então, voltando à sua pergunta.
Quando você passa seu objeto por valor, não por ponteiro ou por referência, copia o objeto (todo o objeto, seja um par de bytes ou um imenso despejo de banco de dados - você é esperto o suficiente para evitar o último, não é ' você?) toda vez que você faz '='. E para acessar os membros do objeto, você usa '.' (ponto).
Quando você passa seu objeto por ponteiro, copia apenas alguns bytes (4 em sistemas de 32 bits, 8 em sistemas de 64 bits), a saber - o endereço desse objeto. E para mostrar isso a todos, você usa esse operador sofisticado '->' ao acessar os membros. Ou você pode usar a combinação de '*' e '.'.
Quando você usa referências, obtém o ponteiro que finge ser um valor. É um ponteiro, mas você acessa os membros através de '.'.
E, para surpreender mais uma vez: quando você declara várias variáveis separadas por vírgulas, observe (observe os ponteiros):
Exemplo:
fonte
std::auto_ptr
está obsoleto, não o use.No C ++, os objetos alocados na pilha (usando a
Object object;
instrução dentro de um bloco) permanecerão apenas dentro do escopo em que foram declarados. Quando o bloco de código termina a execução, o objeto declarado é destruído. Considerando que, se você alocar memória no heap, usandoObject* obj = new Object()
, eles continuarão permanecendo no heap até você ligardelete obj
.Eu criaria um objeto na pilha quando gostaria de usá-lo não apenas no bloco de código que o declarou / alocou.
fonte
Object obj
nem sempre está na pilha - por exemplo, variáveis globais ou membros.Vou comparar como ele funciona dentro do corpo da função se você usar:
Dentro da função, você
myObject
será destruído quando essa função retornar. Portanto, isso é útil se você não precisar do seu objeto fora da sua função. Este objeto será colocado na pilha de threads atual.Se você escrever dentro do corpo da função:
a instância da classe Object apontada por
myObject
não será destruída quando a função terminar e a alocação estiver no heap.Agora, se você é programador Java, o segundo exemplo está mais próximo de como a alocação de objetos funciona em java. Esta linha:
Object *myObject = new Object;
é equivalente a java:Object myObject = new Object();
. A diferença é que, em java, myObject será coletado como lixo, enquanto em c ++ não será liberado, você deve chamar explicitamente em algum lugar `delete myObject; caso contrário, você introduzirá vazamentos de memória.Desde o c ++ 11, você pode usar maneiras seguras de alocações dinâmicas:
new Object
armazenando valores em shared_ptr / unique_ptr.Além disso, os objetos costumam ser armazenados em contêineres, como map-s ou vector-s, e gerenciam automaticamente a vida útil dos seus objetos.
fonte
then myObject will not get destroyed once function ends
Absolutamente será.myObject
ainda será destruído, assim como qualquer outra variável local. A diferença é que seu valor é um ponteiro para um objeto, não o objeto em si, e a destruição de um ponteiro idiota não afeta seu pontapé. Portanto, o objeto sobreviverá à destruição.Tecnicamente, é um problema de alocação de memória, mas aqui estão mais dois aspectos práticos disso. Tem a ver com duas coisas: 1) Escopo, quando você define um objeto sem um ponteiro, não será mais possível acessá-lo após o bloco de código em que está definido, enquanto que se você definir um ponteiro com "novo", poderá você pode acessá-lo de qualquer lugar que tenha um ponteiro para esta memória até chamar "excluir" no mesmo ponteiro. 2) Se você deseja passar argumentos para uma função, deseja passar um ponteiro ou uma referência para ser mais eficiente. Quando você passa um Objeto, o objeto é copiado; se este é um objeto que usa muita memória, isso pode consumir CPU (por exemplo, você copia um vetor cheio de dados). Quando você passa um ponteiro, tudo o que passa é um int (dependendo da implementação, mas a maioria deles é um int).
Fora isso, você precisa entender que "novo" aloca memória no heap que precisa ser liberado em algum momento. Quando você não precisa usar "novo", sugiro que você use uma definição de objeto regular "na pilha".
fonte
Bem, a questão principal é: por que devo usar um ponteiro em vez do próprio objeto? E a minha resposta, você (quase) nunca deve usar ponteiro em vez de objeto, porque o C ++ tem referências , é mais seguro que o ponteiro e garante o mesmo desempenho que o ponteiro.
Outra coisa que você mencionou na sua pergunta:
Como funciona? Ele cria ponteiros do
Object
tipo, aloca memória para caber em um objeto e chama o construtor padrão, soa bem, certo? Mas, na verdade, não é tão bom, se você alocou memória dinamicamente (palavra-chave usadanew
), também precisa liberar memória manualmente, ou seja, no código você deve ter:Isso chama destruidor e libera memória, parece fácil, no entanto, em grandes projetos pode ser difícil detectar se um thread liberou memória ou não, mas para esse objetivo, você pode tentar ponteiros compartilhados , isso diminui um pouco o desempenho, mas é muito mais fácil trabalhar com eles.
E agora algumas apresentações terminam e voltam à questão.
Você pode usar ponteiros em vez de objetos para obter melhor desempenho ao transferir dados entre funções.
Dê uma olhada, você tem
std::string
(também é objeto) e contém realmente muitos dados, por exemplo, XML grande, agora você precisa analisá-los, mas para isso você tem uma funçãovoid foo(...)
que pode ser declarada de diferentes maneiras:void foo(std::string xml);
Nesse caso, você copiará todos os dados da sua variável para a pilha de funções, leva algum tempo, portanto, seu desempenho será baixo.void foo(std::string* xml);
Nesse caso, você passará o ponteiro para o objeto, com a mesma velocidade dasize_t
variável de passagem ; no entanto, esta declaração tem tendência a erros, porque você pode passar oNULL
ponteiro ou o ponteiro inválido. Ponteiros geralmente usadosC
porque não possuem referências.void foo(std::string& xml);
Aqui você passa a referência, basicamente é o mesmo que passar o ponteiro, mas o compilador faz algumas coisas e você não pode passar a referência inválida (na verdade, é possível criar uma situação com referência inválida, mas está enganando o compilador).void foo(const std::string* xml);
Aqui é o mesmo que o segundo, apenas o valor do ponteiro não pode ser alterado.void foo(const std::string& xml);
Aqui é o mesmo que o terceiro, mas o valor do objeto não pode ser alterado.O que mais quero mencionar, você pode usar essas 5 maneiras de passar dados, independentemente da maneira de alocação que você escolheu (com
new
ou regular ).Outra coisa a mencionar, quando você cria um objeto de maneira regular , aloca memória na pilha, mas enquanto o cria,
new
aloca heap. É muito mais rápido para alocar stack, mas é tipo um pequeno para realmente grandes conjuntos de dados, por isso, se você precisar de grande objeto que você deve usar pilha, porque você pode obter estouro de pilha, mas normalmente este problema é resolvido usando contêineres STL e lembre-sestd::string
também é contêiner, alguns caras esqueceram :)fonte
Digamos que você
class A
contenhaclass B
Quando você quiser chamar alguma função declass B
fora,class A
você simplesmente obterá um ponteiro para esta classe e poderá fazer o que quiser e isso também mudará o contexto declass B
sua classe .class A
Mas tenha cuidado com o objeto dinâmico
fonte
Há muitos benefícios em usar ponteiros para objetar -
fonte
Isso foi discutido detalhadamente, mas em Java tudo é um ponteiro. Não faz distinção entre alocações de pilha e heap (todos os objetos são alocados na pilha), portanto você não percebe que está usando ponteiros. No C ++, você pode misturar os dois, dependendo dos requisitos de memória. O desempenho e o uso da memória são mais determinísticos em C ++ (duh).
fonte
Isso criará uma referência a um Objeto (na pilha) que deve ser excluído explicitamente para evitar vazamento de memória .
Isso criará um objeto (myObject) do tipo automático (na pilha) que será excluído automaticamente quando o objeto (myObject) ficar fora do escopo.
fonte
Um ponteiro faz referência direta à localização da memória de um objeto. Java não tem nada assim. Java possui referências que referenciam a localização do objeto através de tabelas de hash. Você não pode fazer nada como aritmética de ponteiro em Java com essas referências.
Para responder sua pergunta, é apenas sua preferência. Eu prefiro usar a sintaxe semelhante a Java.
fonte
Com ponteiros ,
pode falar diretamente com a memória.
pode impedir muitos vazamentos de memória de um programa manipulando ponteiros.
fonte
Uma razão para o uso de ponteiros é a interface com as funções C. Outro motivo é economizar memória; por exemplo: em vez de passar um objeto que contém muitos dados e possui um construtor de cópias com uso intensivo de processador para uma função, basta passar um ponteiro para o objeto, economizando memória e velocidade, especialmente se você estiver em loop, no entanto, um referência seria melhor nesse caso, a menos que você esteja usando uma matriz de estilo C.
fonte
Em áreas onde a utilização da memória é excelente, os ponteiros são úteis. Por exemplo, considere um algoritmo minimax, onde milhares de nós serão gerados usando rotina recursiva e, posteriormente, os utilizará para avaliar a próxima melhor jogada no jogo, a capacidade de desalocar ou redefinir (como em ponteiros inteligentes) reduz significativamente o consumo de memória. Enquanto a variável não-ponteiro continua ocupando espaço até que a chamada recursiva retorne um valor.
fonte
Incluirei um caso de uso importante de ponteiro. Quando você está armazenando algum objeto na classe base, mas pode ser polimórfico.
Portanto, neste caso, você não pode declarar bObj como um objeto direto, você precisa ter um ponteiro.
fonte
"Necessidade é a mãe da invenção." A diferença mais importante que gostaria de destacar é o resultado de minha própria experiência em codificação. Às vezes você precisa passar objetos para funções. Nesse caso, se o seu objeto for de uma classe muito grande, passá-lo como um objeto copiará seu estado (o que você pode não querer ... E PODE SER MUITO GRANDE), resultando em uma sobrecarga de cópia do objeto. Enquanto o ponteiro for corrigido Tamanho de 4 bytes (assumindo 32 bits). Outras razões já foram mencionadas acima ...
fonte
std::string test;
que temosvoid func(const std::string &) {}
, mas a menos que a função precisa mudar a entrada nesse caso, eu recomendo o uso de ponteiros (de modo que qualquer um que lê o código faz aviso&
, e entende a função pode mudar a sua entrada)Já existem muitas respostas excelentes, mas deixe-me dar um exemplo:
Eu tenho uma classe simples de itens:
Eu faço um vetor para segurar um monte deles.
std::vector<Item> inventory;
Crio um milhão de objetos Item e os empurro de volta para o vetor. Classifico o vetor por nome e, em seguida, faço uma pesquisa binária iterativa simples para um nome de item específico. Eu testo o programa e leva mais de 8 minutos para concluir a execução. Então eu altero meu vetor de inventário da seguinte forma:
std::vector<Item *> inventory;
... e crie meus milhões de objetos de itens por meio de novos. As únicas alterações que faço no meu código são usar os ponteiros para os itens, exceto um loop que adiciono para limpeza de memória no final. Esse programa é executado em menos de 40 segundos, ou melhor que um aumento de velocidade de 10x. EDIT: O código está em http://pastebin.com/DK24SPeW Com as otimizações do compilador, ele mostra apenas um aumento de 3,4x na máquina em que acabei de testá-lo, o que ainda é considerável.
fonte
push_back
. Claro que isso copia. Você deveria estaremplace
no local ao criar seus objetos (a menos que precise que eles sejam armazenados em cache em outro lugar).