Alguém poderia me explicar a diferença desses dois códigos? Pelo que entendi ambos criam uma nova pessoa!
Pessoa homem ("Nome", 22);
Pessoa *p = new Pessoa("Nome2", 1);
Alguém poderia me explicar a diferença desses dois códigos? Pelo que entendi ambos criam uma nova pessoa!
Pessoa homem ("Nome", 22);
Pessoa *p = new Pessoa("Nome2", 1);
O segundo caso é um ponteiro, ao usar ponterio você pode acessar diretamente o valor guardado na variável p.
Pessoa *p = new Pessoa("Nome2", 1);
Se ambas as variáveis “pessoa” forem locais, você vai ver que o primeiro objeto é criado na pilha (stack) e o segundo no heap, e acessível via ponteiros.
É o que o bezier falou. No caso do C++, você pode dizer em que área de memória os objetos serão criados. Existem duas possibilidades, cria-las no heap ou no stack.
Tudo que é criado no stack é automaticamente destruído assim que sai de escopo. Tudo que é criado no heap (usando new), deve ser explicitamente destruído em algum momento, usando delete.
Então, é preciso tomar certos cuidados:
//A variável p será destruída assim que a função acabar, logo a referência à p não será válida
const Pessoa& Pessoa::criarPessoa() const {
Pessoa p("Vinicius", 100);
return p;
}
//p receberá um dangling pointer, e o programa executará operação ilegal e será fechado ao tentar acessar p.
Pessoa& p = criarPessoa();
Note que esse código poderia ser corrigido com o uso do new:
//A variável p será destruída assim que a função acabar, logo a referência à p não será válida
Pessoa* Pessoa::criarPessoa() const {
Pessoa* p = new Pessoa("Vinicius", 100);
return p;
} //ok, p está no heap, não será destruído
Pessoa* p = criarPessoa();
delete p; //Mas ele precisa ser deletado, ou vc um memory leak.
hmmm entendi… obrigado pessoal!
Já que você citou esse assunto, vale a pena falar um pouco sobre a técnica de RAII.
RAII é a sigla para Resource Acquisition is Initialization. A idéia por trás dela é que sempre que um recurso (como memória) for iniciado e tiver que ser posteriormente destruído, você também deverá criar um objeto no stack para gerenciar esse recurso. Considere o seguinte código:
void Classe::fazQualquerCoisa()
{
ObjetoQualquer* oq = new ObjetoQualquer();
fazQualquerCoisa1();
oq->fazQualquerCoisa();
delete oq;
}
Esse código tem leaks?
A resposta é, sim! Ele pode ter leaks. O que acontece se uma exception for disparada pelo método fazQualquerCoisa1() ou oq->fazQualquerCoisa()? A resposta é: aquele delete não roda, o ponteiro para oq se perde, mas o objeto fica reservado. Problemaço.
Poderíamos colocar o código todo num try...catch:void Classe::fazQualquerCoisa()
{
try
{
ObjetoQualquer* oq = new ObjetoQualquer();
fazQualquerCoisa1();
oq->fazQualquerCoisa();
}
catch (...)
{
delete oq;
throw;
}
delete oq;
}
Mas isso gera um código rebuscado, e sempre há a chance de um programador distraído colocar um return, dentro da linha do try. Qual seria então a melhor solução? Usar RAII.
Primeiro, criamos uma classe, que irá cuidar do ObjetoQualquer. Ela simplesmente guarda o ponteiro para ObjetoQualquer e se encarrega de destrui-lo em seu destrutor.
class ObjetoQualquerManager {
private:
ObjetoQualquer* pObjetoQualquer;
public:
ObjetoQualquerManager(ObjetoQualquer* obj)
: pObjetoQualquer(obj)
{
}
public ObjetoQualquer* operator -> ()
{
return pObjetoQualquer;
}
public const ObjetoQualquer* operator -> () const
{
return pObjetoQualquer;
}
~ObjetoQualquer()
{
delete pObjetoQualquer;
}
};
void Classe::fazQualquerCoisa()
{
ObjetoQualquerManager(new ObjetoQualquer());
fazQualquerCoisa1();
oq->fazQualquerCoisa();
}
O que acontece agora? ObjetoQualquerManager é um objeto do stack. Lembra-se que falei que a destruição dele era garantida ao final do método? Pois bem, continua garantida, mesmo que sejam dados returns ou estourem exceptions. Mas o que ocorre nessa destruição? O destrutor do manager é chamado, e isso faz com que o delete seja invocado em ObjetoQualquer.
Facilmente, poderíamos reformular essa classe com templates, para que ela se adaptasse para qualquer objeto. Note que eu sobrescrevi o operador de -> para que ele se comportasse de maneira similar a um ponteiro. De fato, poderíamos bolar um template que se comportasse exatamente igual a um ponteiro, mas um ponteiro inteligente, que fizesse essa deleção para nós.
Esse tipo de classe é chamado Smart Pointer. A biblioteca boost, do C++, tem vários ponteiros desse tipo, alguns bem inteligentes com contagem de referências, que tornam o uso do C++ muito parecido com o de uma linguagem com garbage collection. Quem tiver interesse, pode ver a série de artigos sobre smart pointers no Ponto V!: Smart Pointers - Índice
Só cuidado que o índice encontra-se da postagem mais recente até a mais velha, portanto, para ler em série, comece do 8 e vá até o 1.
Aliás, falando em new e delete, também podemos citar algumas situações cruéis do C++. Considere a seguinte class:
class Xyz {
private:
Abc* abc;
public:
Xyz() : abc(new Abc()) {}
imprime() { cout << "O valor de abc é:" << abc; }
~Xyz() { delete abc; }
};
Considere que abc possa ser impresso usando o cout, e que não há eventuais erros de digitação da minha parte, e que o std::cout está importado. Ok, essa classe tem algum problema? Aparentemente não, o código roda as mil maravilhas, o delete é dado no destrutor. Certo?
Então, um programador que manja de Java, mas não de C++, faz o seguinte:void teste(const Xyz& xyz1) {
Xyz xyz = xyz1;
xyz.imprime();
}
int main(int argc, char* argv) {
Xyz xyz;
tete(xyz);
xyz.imprime();
}
Tudo certo com esse código, correto? Errado. Esse código irá dar problema na última linha do main.
Mas por que????
A primeira linha do método teste, fez uma cópia do objeto xyz. Note que
Xyz xyz = xyz1;
Está criando um objeto no stack, não um ponteiro ou uma referência. Então, o construtor de cópia padrão do C++ é chamado. O construtor de cópia padrão faz o mesmo que o clone padrão do java. Simplesmente reproduz os valores das variáveis internas, cegamente. Isso inclui copiar o valor do ponteiro pra abc.
Quanto o método teste acaba o objeto xyz é destruído. Com isso, é chamado um delete seu ponteiro abc, que aponta para o mesmo lugar do ponteiro do objeto xyz1 recebido como parâmetro! Ao sair do método, portanto, o xyz do main está com um dangling pointer. Um de seus ponteiros internos já não aponta para lugar nenhum.
Vem a linha xyz.imprime() no main e:
"Este programa executou um comando ilegal e será fechado!" (sorry, my friend, mas vc não pode acessar essa área de memória, isso não te pertence mais!!)
class Xyz {
private:
Abc* abc;
Xyz(const Xyz&); //Desligamos o construtor de cópia
Xyz& operator = (const Xyz&); //Desligamos a cópia pelo operador de =
public:
Xyz() : abc(new Abc()) {}
imprime() { cout << "O valor de abc é:" << abc; }
~Xyz() { delete abc}
};
Agora, aquele nosso código passa a dar erro na linha 2 do método teste. Ele diz que
Xyz xyz = xyz1;
É uma operação inválida, já que Xyz não tem construtor de cópia.
Smart pointers também podem resolver esse problema. Alguns fazem contagem de referência, permitindo compartilhar o objeto, outros desabilitam a cópia.
Po show de bola ein… temos que pegar todos os posts do Vini e fazer um livro “Dicas de como programar em Java e C++” hehe
brigadao kra
Po show de bola ein… temos que pegar todos os posts do Vini e fazer um livro “Dicas de como programar em Java e C++” hehebrigadao kra
É, eu as vezes me empolgo…
Por isso existem livros como
Linguagem X para programadores Y.
Estes posts são preciosos, poderiam render artigos, posts e muito mais.