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. delete
chama esse destruidor para você.
{
std::string* s = new std::string;
}
delete s; // destructor called
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
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::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:
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 as
palavra - chave. Portanto, se alguém quisesse tratar um Entity
objeto como um Player
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:
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 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.
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 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.