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
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?
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:
{
std::string s;
}
// s is destroyed here
Por outro lado, se você usar um ponteiro alocado dinamicamente, seu destruidor deverá ser chamado manualmente. deletechama esse destruidor para você.
{
std::string* s = new std::string;
}
delete s; // destructor called
Isso não tem nada a ver com a newsintaxe predominante em C # e Java. Eles são usados para finalidades completamente diferentes.
Benefícios da alocação dinâmica
1. Você não precisa saber o tamanho da matriz antecipadamente
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:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
Obviamente, se você usou um std::string, std::stringredimensiona-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:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
Nota lateral : Um erro que muitos iniciantes cometem é o uso de matrizes de comprimento variável. Esta é uma extensão GNU e também uma em Clang porque elas refletem muitas extensões do GCC. Portanto, o seguinte
int arr[n]não deve ser invocado.
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.
2. Matrizes não são ponteiros
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 suasizeof informações. Assim sizeof(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:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
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.
3. Polimorfismo
Java e C # têm recursos que permitem tratar objetos como outro, por exemplo, usando a aspalavra - chave. Portanto, se alguém quisesse tratar um Entityobjeto como um Playerobjeto, 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:
std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
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 se testé 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 ouvalgrind (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 newpalavra - 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.
1. (N) RVO - Aka, (nomeado) Otimização do valor de retorno
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 deleteum 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.