C++

9 respostas
renamed

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);

9 Respostas

A

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);
B

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.

ViniGodoy

É 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:

  1. Não retornar ponteiros de variáveis criadas no stack:
//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.
  1. Não deletar objetos que estejam sendo referenciados por outros objetos;
  2. Se preocupar sempre em quem dará o delete no objeto criado com new.
renamed

hmmm entendi… obrigado pessoal!

ViniGodoy

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;
        }
};
Então, alteramos nosso código para:
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.

ViniGodoy

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!!)

Sorte que é possível desabilitar o construtor e o operador de cópia padrão no C++. E é aconselhável que se faça isso sempre. Para isso, basta declara-los na sessão private da sua classe, e não é necessário nem fornecer implementação:
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.

renamed

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

ViniGodoy

renamed:
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

É, eu as vezes me empolgo…

peczenyj

Por isso existem livros como

Linguagem X para programadores Y.

Estes posts são preciosos, poderiam render artigos, posts e muito mais.

Criado 17 de dezembro de 2009
Ultima resposta 19 de dez. de 2009
Respostas 9
Participantes 5