Prévia do material em texto
C/C++ C/C++ C++ Smart Pointers Maria Luiza Sperancin Mancebo Palavras-chaves: ponteiros brutos, ponteiros inteligentes, memória, unique_ptr, shared_ptr 1. Diferenças (2) Um ponteiro bruto em C++ é uma variável que armazena o endereço de memória de outro valor ou função. Ele tem três propósitos principais: ● Alocar novos objetos no heap, ● Passar funções para outras funções, ● Iterar sobre os elementos em matrizes ou outras estruturas de dados. Com um ponteiro, você pode fazer referência a outros espaços de memória, o que pode ser extremamente útil. No entanto, os ponteiros brutos podem ser difíceis de gerenciar, pois você precisa se lembrar de desalocar qualquer memória que você alocou para evitar vazamentos de memória. int* ponteiro_bruto = new int(10); // cria um ponteiro bruto para um int delete ponteiro_bruto; // desaloca a memória Figura 1 Na figura 1, é criado um ponteiro bruto para um int, e logo em seguida ele é desalocado. Isso é feito já que como citado anteriormente, é necessário desalocar sua memória para evitar vazamentos. Os ponteiros inteligentes são objetos que se parecem e se sentem como ponteiros, mas são mais seguros porque gerenciam a memória automaticamente. Eles foram introduzidos para resolver problemas comuns com ponteiros brutos, como vazamentos de memória e ponteiros pendurados. Existem três tipos principais de ponteiros inteligentes em C++: unique_ptr, shared_ptr e weak_ptr. std::unique_ptr<int> ponteiro_inteligente(new int(10)); // cria um ponteiro // inteligente para um int // a memória é automaticamente desalocada quando ponteiro_inteligente sai de escopo Figura 2 Na figura 2, por sua vez, é criado um ponteiro inteligente e não é feita sua desalocação, já que eles gerenciam automaticamente a memória, e essa é a principal diferença entre eles e os ponteiros brutos. Quando um ponteiro inteligente sai de escopo, ele automaticamente desaloca a memória para a qual aponta. Isso ajuda a prevenir vazamentos de memória, que são um problema comum ao usar ponteiros brutos. Por outro lado, os ponteiros brutos são mais leves e têm menos sobrecarga do que os ponteiros inteligentes. Eles também permitem um controle mais preciso sobre a memória, o que pode ser necessário em alguns casos. 2. Perigos (4) Os principais perigos que existem na utilização de ponteiros brutos são: a) Vazamento de Memória Um dos perigos mais comuns ao usar ponteiros brutos é o vazamento de memória. Isso ocorre quando a memória é alocada, mas não é desalocada corretamente. Se um ponteiro bruto que aponta para uma alocação de memória é sobrescrito ou sai de escopo sem primeiro desalocar a memória, essa memória não pode ser recuperada até que o programa termine. Isso pode levar a um uso significativo e desnecessário de memória, que se torna especialmente problemático em programas de longa duração. Além disso, o vazamento de memória em C++ pode ocorrer devido a exceções não capturadas. Quando uma exceção é lançada, mas não é capturada em nenhum bloco de captura, o programa termina abruptamente. Se a exceção foi lançada durante a execução de um bloco de código que alocou memória dinamicamente, mas ainda não a desalocou, essa memória é perdida. b) Ponteiros Pendentes Outro perigo são os ponteiros pendentes. Isso ocorre quando você tem um ponteiro que aponta para um objeto que foi desalocado. Tentar acessar o objeto através do ponteiro pendurado pode levar a comportamento indefinido, pois a memória anteriormente ocupada pelo objeto pode agora conter diferentes dados, ou pode ter sido liberada de volta para o sistema. Além disso, os ponteiros pendentes podem ser particularmente perigosos porque o comportamento do programa pode variar. Como a memória que o ponteiro pendente aponta pode ser realocada, o ponteiro pode apontar para dados que parecem válidos, mas na verdade são completamente diferentes do objeto original. Isso pode levar a erros sutis e difíceis de rastrear. Os ponteiros pendentes também podem levar a violações de segurança. Se um invasor puder manipular o programa para escrever dados na memória apontada por um ponteiro pendente, ele poderá ser capaz de executar código arbitrário ou acessar informações sensíveis. c) Acesso Fora dos Limites Os ponteiros brutos não têm informações sobre o tamanho dos dados para os quais estão apontando. Isso pode levar a erros de acesso fora dos limites, onde o programa tenta acessar a memória além do final do objeto alocado. Isso pode resultar em comportamento indefinido. d) Incompatibilidade de Tipo Os ponteiros brutos em C++ são fracamente tipados. Isso significa que um ponteiro de um tipo pode ser convertido em um ponteiro de outro tipo sem qualquer verificação de tipo em tempo de compilação. Isso pode levar a erros se o ponteiro for desreferenciado e o dado for interpretado como o tipo errado. 3. Unique_ptr (5) O unique_ptr é um tipo de ponteiro inteligente introduzido no C++11 como parte da biblioteca padrão. Ele foi projetado para gerenciar a propriedade de um objeto único, garantindo que o objeto seja destruído corretamente quando o unique_ptr sai do escopo. Algumas características importantes do unique_ptr são: a) Propriedade Única A propriedade única é uma característica fundamental do unique_ptr que garante que apenas um unique_ptr possa possuir um objeto específico a qualquer momento. Isso é importante por várias razões, como evitar a duplicação de liberação de memória, ou seja, se dois ponteiros apontarem para o mesmo objeto, poderia haver um risco de que o objeto seja deletado duas vezes, o que pode levar a comportamentos indefinidos. Com a propriedade única, esse problema é evitado porque apenas um unique_ptr pode possuir o objeto, garantindo que ele seja deletado apenas uma vez. Também, a propriedade única traz uma claridade de propriedade, isso é, tornar claro quem é responsável por deletar o objeto. Isso pode simplificar o código e torná-lo mais fácil de entender, já que não há dúvidas sobre quem deve liberar a memória. Além disso, em um ambiente multi-thread, a propriedade única pode ajudar a evitar condições de corrida relacionadas à desalocação de memória. Como apenas um unique_ptr pode possuir o objeto, não há risco de que dois threads tentem deletar o objeto simultaneamente. b) Gerenciamento Automático de Memória Quando um unique_ptr sai do escopo, o destrutor é chamado automaticamente e o objeto ao qual ele aponta é destruído. Isso ajuda a prevenir vazamentos de memória. c) Transferência de Propriedade A propriedade de um objeto pode ser transferida de um unique_ptr para outro usando o método std::move(). Isso é útil quando você precisa passar a propriedade de um objeto para uma função ou retornar um objeto de uma função. 4. Shared_ptr (6) O shared_ptr é um tipo de ponteiro inteligente que permite a propriedade compartilhada de um objeto. Isso significa que vários shared_ptr podem possuir o mesmo objeto e o objeto só será destruído quando todos os outros que apontam para ele forem destruídos. Algumas características importantes do shared_ptr são: a) Gerenciamento de Propriedade Compartilhada: O shared_ptr permite que vários ponteiros apontem para o mesmo objeto. Isso é útil quando você precisa compartilhar um objeto entre diferentes partes do seu programa sem se preocupar com quem deve deletar o objeto. b) Contagem de Referência: O shared_ptr mantém uma contagem de referência para rastrear quantos ponteiros estão apontando para o mesmo objeto. Quando essa contagem cai para zero (ou seja, quando não há mais shared_ptr apontando para o objeto), o objeto é automaticamente destruído. c) Gerenciamento Automático de Memória: Assim como unique_ptr, quando um shared_ptr é destruído (por exemplo, quando sai do escopo), ele automaticamente destrói o objeto ao qual aponta se a contagem de referência for zero. 5. Desempenho (10) Embora os ponteiros inteligentes possam ajudar a prevenir muitos bugs comuns de gerenciamento de memória, eles vêm com suas próprias considerações de desempenho que devem serlevadas em conta. É importante entender essas trocas ao decidir usar ponteiros inteligentes em seu código. Aqui estão algumas considerações de desempenho ao usar esses ponteiros: a) Sobrecarga de Contagem de Referência Ponteiros inteligentes como shared_ptr mantém uma contagem de referência para gerenciar a vida útil do objeto apontado. Cada vez que um shared_ptr é copiado, a contagem de referência é incrementada e cada vez que é destruído, a contagem de referência é decrementada. Se a contagem de referência chega a zero, o objeto apontado é destruído. Esta contagem de referência adiciona uma sobrecarga ao desempenho, especialmente em operações de cópia. b) Contenção de Bloqueio A contagem de referência em shared_ptr é thread-safe, o que significa que pode ser acessada por vários threads simultaneamente sem causar condições de corrida. No entanto, isso é alcançado através do uso de operações atômicas, que podem causar contenção de bloqueio se muitos threads estiverem tentando acessar o mesmo shared_ptr simultaneamente. Isso pode levar a uma degradação do desempenho em programas multithreaded. c) Sobrecarga de Memória Ponteiros inteligentes usam mais memória do que ponteiros brutos. Por exemplo, um shared_ptr precisa armazenar o ponteiro para o objeto, a contagem de referência e, possivelmente, um deleter personalizado. Isso pode ser significativo em programas que usam uma grande quantidade de ponteiros. No entanto, esse overhead é justificado pela segurança e gerenciamento automático de memória que os ponteiros inteligentes oferecem. Bibliografia MICROSOFT. Ponteiros brutos (C++). Disponível em: <https://learn.microsoft.com/pt-br/cpp/cpp/raw-pointers?view=msvc-170>. Acesso em: 21 mar. 2024. MICROSOFT. Ponteiros inteligentes (C++ moderno). Disponível em: <https://learn.microsoft.com/pt-br/cpp/cpp/smart-pointers-modern-cpp?view=msvc-170>. Acesso em: 22 mar. 2024. MICROSOFT. Classe shared_ptr. Disponível em: <https://learn.microsoft.com/pt-br/cpp/standard-library/shared-ptr-class?view=msvc-170>. Acesso em: 23 mar. 2024. MICROSOFT. Classe unique_ptr. Disponível em: <https://learn.microsoft.com/pt-br/cpp/standard-library/unique-ptr-class?view=msvc-170>. Acesso em: 22 mar. 2024. MEDIUM. O que é memory leak e como evitar. Disponível em <https://xfgusta.medium.com/o-que-%C3%A9-memory-leak-e-como-evitar-af7941c334f6>. Acesso em: 22 mar. 2024. BABATI, Bence; PATAKI, Norbert. Comprehensive Performance Analysis of C++ Smart Pointers. Faculty of Informatics, Eötvös Loránd University, 2017. Disponível em: <https://real.mtak.hu/66602/1/606.2017.12.3.14.pdf>. Acesso em: 25 mar. 2024. https://learn.microsoft.com/pt-br/cpp/cpp/raw-pointers?view=msvc-170 https://learn.microsoft.com/pt-br/cpp/cpp/smart-pointers-modern-cpp?view=msvc-170 https://learn.microsoft.com/pt-br/cpp/standard-library/shared-ptr-class?view=msvc-170 https://learn.microsoft.com/pt-br/cpp/standard-library/unique-ptr-class?view=msvc-170 https://xfgusta.medium.com/o-que-%C3%A9-memory-leak-e-como-evitar-af7941c334f6 https://real.mtak.hu/66602/1/606.2017.12.3.14.pdf