Por que bibliotecas e estruturas C ++ nunca usam ponteiros inteligentes?


156

Li em alguns artigos que ponteiros brutos quase nunca deveriam ser usados. Em vez disso, eles sempre devem ser agrupados em ponteiros inteligentes, sejam eles de escopo ou compartilhados.

No entanto, notei que estruturas como Qt, wxWidgets e bibliotecas como Boost nunca retornam nem esperam indicadores inteligentes, como se não os estivessem usando. Em vez disso, eles retornam ou esperam ponteiros brutos. Há alguma razão para isso? Devo ficar longe de indicadores inteligentes quando escrevo uma API pública e por quê?

Basta saber por que os indicadores inteligentes são recomendados quando muitos projetos importantes parecem evitá-los.


22
Todas as bibliotecas que você acabou de nomear foram iniciadas há muitos anos. Ponteiros inteligentes só se tornaram verdadeiramente padrão no C ++ 11.
Chrisaycock #

22
ponteiros inteligentes têm uma sobrecarga (contagem de referência, etc.) - que pode ser crítica - em sistemas embarcados / em tempo real, por exemplo. IMHO - indicadores inteligentes são para programadores preguiçosos. Além disso, muitas APIs buscam o menor denominador comum. Sinto as chamas lambendo meus pés enquanto digito!
Ed Heal #

93
@ Edheal: A razão pela qual você pode sentir chamas lambendo seus pés é porque você está totalmente errado em todos os aspectos. Por exemplo, em que sobrecarga existe unique_ptr? Nenhuma. O Qt / WxWidgets é direcionado para sistemas embarcados ou em tempo real? Não, eles são destinados ao Windows / Mac / Unix em uma área de trabalho - no máximo. Ponteiros inteligentes são para programadores que desejam corrigi-lo.
Filhote de cachorro

24
Realmente, os telefones móveis estão executando Java.
R. Martinho Fernandes

12
Ponteiros inteligentes apenas verdadeiramente padrão em C ++ 11? O que??? Essas coisas são usadas há mais de 20 anos.
Kaz

Respostas:


124

Além do fato de muitas bibliotecas terem sido escritas antes do advento dos ponteiros inteligentes padrão, o maior motivo é provavelmente a falta de uma ABI (Interface Binária de Aplicativo) C ++ padrão.

Se você estiver escrevendo uma biblioteca apenas de cabeçalho, pode passar ponteiros inteligentes e contêineres padrão para o conteúdo do seu coração. A fonte deles está disponível para sua biblioteca em tempo de compilação, portanto você depende apenas da estabilidade de suas interfaces, não de suas implementações.

Mas, devido à falta de ABI padrão, geralmente você não pode passar esses objetos com segurança através dos limites do módulo. Um GCC shared_ptrprovavelmente é diferente de um MSVC shared_ptr, que também pode ser diferente de um Intel shared_ptr. Mesmo com o mesmo compilador, essas classes não têm garantia de compatibilidade binária entre versões.

O ponto principal é que, se você deseja distribuir uma versão pré - construída da sua biblioteca, precisa de uma ABI padrão na qual confiar. C não possui um, mas os fornecedores de compiladores são muito bons em interoperabilidade entre bibliotecas C para uma determinada plataforma - existem padrões de fato.

A situação não é tão boa para C ++. Compiladores individuais podem lidar com a interoperação entre seus próprios binários, para que você tenha a opção de distribuir uma versão para cada compilador suportado, geralmente o GCC e o MSVC. Mas, à luz disso, a maioria das bibliotecas apenas exporta uma interface C - e isso significa ponteiros brutos.

No entanto, o código que não é de biblioteca geralmente prefere ponteiros inteligentes em vez de dados brutos.


17
Eu concordo com você, mesmo passando um std :: string pode ser uma dor, isso diz muito sobre C ++ como uma "ótima linguagem para bibliotecas".
usar o seguinte comando

8
A linha inferior é mais parecida: se você deseja distribuir uma versão pré-compilada, deve fazê-lo para todos os compiladores que deseja oferecer suporte.
josefx

6
@josefx: Sim, isso é triste, mas é verdade, a única alternativa é COM ou uma interface C bruta. Eu gostaria que a comunidade C ++ começasse a se preocupar com esse tipo de problema. Quero dizer, não é como se o C ++ fosse uma nova linguagem de 2 anos atrás.
9788 Robot Data

3
Eu votei mal porque isso está errado. Os problemas da ABI são mais do que gerenciáveis ​​na maioria dos casos. Embora dificilmente fácil de usar, a ABI também é dificilmente intransponível.
Filhote de cachorro

4
@ NathanAdams: Esse software é, sem dúvida, impressionante e útil. Mas trata o sintoma de problemas mais profundos: a semântica em C ++ da vida e da propriedade está entre empobrecida e inexistente. Esses erros de heap não teriam surgido se o idioma não os permitisse. Portanto, os indicadores inteligentes não são uma panacéia - eles são uma tentativa de recuperar algumas das perdas incorridas usando C ++ em primeiro lugar.
22413 Jon Purdy

40

Pode haver muitas razões. Para listar alguns deles:

  1. Ponteiros inteligentes passaram a fazer parte do padrão recentemente. Até então eles faziam parte de outras bibliotecas
  2. Seu uso principal é evitar vazamentos de memória; muitas bibliotecas não têm seu próprio gerenciamento de memória; Geralmente eles fornecem utilitários e APIs
  3. Eles são implementados como wrapper, pois na verdade são objetos e não ponteiros. Que possui custo adicional de tempo / espaço, comparado aos indicadores brutos; Os usuários das bibliotecas podem não querer ter essas despesas gerais

Editar : O uso de ponteiros inteligentes é uma escolha totalmente do desenvolvedor. Depende de vários fatores.

  1. Em sistemas críticos de desempenho, talvez você não queira usar ponteiros inteligentes que geram sobrecarga

  2. O projeto que precisa de compatibilidade com versões anteriores, talvez você não queira usar os ponteiros inteligentes que possuem recursos específicos do C ++ 11

Edit2 Há uma série de várias votações negativas no período de 24 horas por causa da passagem abaixo. Não consigo entender por que a resposta foi reduzida, mesmo que abaixo seja apenas uma sugestão de complemento e não uma resposta.
No entanto, o C ++ sempre facilita a abertura das opções. :) por exemplo

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

E no seu código, use-o como:

Pointer<int>::type p;

Para aqueles que dizem que um ponteiro inteligente e um ponteiro bruto são diferentes, eu concordo com isso. O código acima era apenas uma idéia em que se pode escrever um código que é intercambiável apenas com a #define, isso não é compulsão ;

Por exemplo, T*deve ser excluído explicitamente, mas um ponteiro inteligente não. Podemos ter um modelo Destroy()para lidar com isso.

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

e use-o como:

Destroy(p);

Da mesma forma, para um ponteiro bruto, podemos copiá-lo diretamente e para ponteiro inteligente, podemos usar uma operação especial.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Onde Assign()está como:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}

14
Em 3. Alguns ponteiros inteligentes têm custos adicionais de tempo / espaço, outros não, inclusive o std::auto_ptrque faz parte do padrão há muito tempo (e observe que eu gosto std::auto_ptrdo tipo de retorno para funções que criam objetos, mesmo que seja quase inútil em qualquer outro lugar). No C ++ 11 std::unique_ptr, não há custos adicionais sobre um ponteiro simples.
David Rodríguez - dribeas

4
Exatamente ... existe uma boa simetria na aparência unique_ptre no desaparecimento de auto_ptr, o código direcionado ao C ++ 03 deve ser usado posteriormente, enquanto o código direcionado ao C ++ 11 pode usar o primeiro. Ponteiros inteligentes são não shared_ptr , há muitos padrão e nenhum padrão, incluindo propostas para o padrão que foram rejeitadas comomanaged_ptr
David Rodríguez - dribeas

2
@iammilind, esses são pontos interessantes, mas o engraçado é que, se acabarmos usando ponteiros inteligentes, como aparentemente muitos recomendariam, acabaremos criando código incompatível com as principais bibliotecas. Obviamente, podemos empacotar / desembrulhar os ponteiros inteligentes conforme necessário, mas isso parece muito complicado e criaria código inconsistente (às vezes lidamos com ponteiros inteligentes, outras vezes não).
laurent

7
A afirmação de que ponteiros inteligentes têm "custo adicional de tempo / espaço" é um tanto enganadora; todos os ponteiros inteligentes, exceto unique_ptrincorrem no custo de tempo de execução, mas unique_ptrsão de longe o mais usado. O exemplo de código que você fornece também é enganoso, porque unique_ptre T*são conceitos totalmente diferentes. O fato de você se referir a ambos typedá a impressão de que eles podem ser trocados um pelo outro.
void-pointer

12
Você não pode digitar os caracteres dessa maneira, esses tipos não são de forma alguma equivalentes. Escrever typedefs como este está causando problemas.
Alex B

35

Há dois problemas com ponteiros inteligentes (pré C ++ 11):

  • fora dos padrões, então cada biblioteca tende a reinventar a sua própria (questões de síndromes e dependências do NIH)
  • custo potencial

O ponteiro inteligente padrão , por ser gratuito, é unique_ptr. Infelizmente, requer a semântica de movimentação do C ++ 11, que apareceu apenas recentemente. Todos os outros ponteiros inteligentes têm um custo ( shared_ptr, intrusive_ptr) ou têm semântica abaixo da ideal ( auto_ptr).

Com o C ++ 11 ao virar da esquina, trazendo um std::unique_ptr, seria possível pensar que finalmente acabou ... Não estou tão otimista.

Apenas alguns compiladores principais implementam a maior parte do C ++ 11 e somente em suas versões recentes. Podemos esperar que grandes bibliotecas, como QT e Boost, estejam dispostas a manter a compatibilidade com o C ++ 03 por um tempo, o que de certa forma impede a ampla adoção dos novos e brilhantes ponteiros inteligentes.


12

Você não deve ficar longe de ponteiros inteligentes, eles têm seu uso especialmente em aplicativos em que você precisa passar um objeto.

As bibliotecas tendem a retornar apenas um valor ou preencher um objeto. Eles geralmente não têm objetos que precisam ser usados ​​em muitos lugares, portanto, não há necessidade de usar ponteiros inteligentes (pelo menos não na interface, eles podem usá-los internamente).

Eu poderia tomar como exemplo uma biblioteca em que estivemos trabalhando, onde, após alguns meses de desenvolvimento, percebi que só usamos ponteiros e ponteiros inteligentes em algumas classes (3-5% de todas as classes).

Passar variáveis ​​por referência foi suficiente na maioria dos lugares, usamos ponteiros inteligentes sempre que tínhamos um objeto que poderia ser nulo e ponteiros brutos quando uma biblioteca que usamos nos forçava.

Editar (não posso comentar por causa da minha reputação): passar variáveis ​​por referência é muito flexível: se você deseja que o objeto seja somente leitura, pode usar uma referência const (você ainda pode fazer algumas projeções desagradáveis ​​para poder escrever o objeto ), mas você obtém o máximo de proteção possível (o mesmo acontece com os ponteiros inteligentes). Mas concordo que é muito melhor apenas retornar o objeto.


Não concordo exatamente com você, mas vou apontar que existe uma escola de pensamento que deprecia a passagem de referências variáveis na maioria dos casos. Confesso que aderir a essa escola. Prefiro funções para não modificar seus argumentos. De qualquer forma, até onde eu sei, as referências variáveis ​​do C ++ não fazem nada para impedir o manuseio incorreto dos objetos aos quais eles se referem, que é o que ponteiros inteligentes pretendem fazer.
THB

2
você tem const para isso (parece que posso comentar: D).
Robot Mess

9

O Qt reinventou inutilmente muitas partes da biblioteca Standard, na tentativa de se tornar Java. Acredito que atualmente ele tenha seus próprios indicadores inteligentes, mas, em geral, dificilmente é um pináculo do design. Até onde sei, o wxWidgets foi projetado muito antes da escrita de ponteiros inteligentes utilizáveis.

Quanto ao Boost, espero que eles usem ponteiros inteligentes sempre que apropriado. Você pode ter que ser mais específico.

Além disso, não esqueça que existem indicadores inteligentes para reforçar a propriedade. Se a API não possui semântica de propriedade, por que usar um ponteiro inteligente?


19
O Qt foi escrito antes que grande parte da funcionalidade fosse suficientemente difundida nas plataformas que ele queria usar. Ele possui indicadores inteligentes há muito tempo e os utiliza para fazer o compartilhamento implícito de recursos em quase todas as classes Q *.
rubenvb

6
Toda biblioteca de GUI reinventa desnecessariamente a roda. Mesmo strings, Qt tem QString, wxWidgets wxString, MFC tem o nome horrível CString. Um UTF-8 não é std::stringbom o suficiente para 99% das tarefas da GUI?
Inverse

10
@ QString inverso foi criado quando std :: string não estava por perto.
perfil completo de MrFox

Verifique quando o qt foi criado e quais indicadores inteligentes estavam disponíveis naquele momento.
Dainius

3

Boa pergunta. Não conheço os artigos específicos aos quais você se refere, mas li coisas semelhantes de tempos em tempos. Minha suspeita é que os autores desses artigos tendem a ter um viés contra a programação no estilo C ++. Se o gravador programar em C ++ somente quando for necessário, retornar ao Java ou assim que puder, ele realmente não compartilhará a mentalidade de C ++.

Suspeita-se que alguns ou a maioria dos mesmos escritores prefiram gerenciadores de memória coletores de lixo. Eu não, mas penso de forma diferente do que eles.

Os indicadores inteligentes são ótimos, mas precisam manter as contagens de referência. A manutenção das contagens de referência suporta custos - geralmente custos modestos, mas, no entanto - no tempo de execução. Não há nada errado em economizar esses custos usando ponteiros simples, especialmente se os ponteiros forem gerenciados por destruidores.

Uma das coisas excelentes sobre C ++ é o suporte à programação de sistemas embarcados. O uso de indicadores simples faz parte disso.

Atualização: Um comentarista observou corretamente que o novo C ++ unique_ptr(disponível desde TR1) não conta referências. O comentarista também tem uma definição diferente de "ponteiro inteligente" do que eu tenho em mente. Ele pode estar certo sobre a definição.

Atualização adicional: o tópico de comentário abaixo está iluminado. Tudo isso é leitura recomendada.


2
Para começar, a programação de sistemas embarcados é uma grande minoria de toda a programação e é irrelevante. C ++ é uma linguagem de uso geral. Em segundo lugar, shared_ptrmantém uma contagem de referência. Existem muitos outros tipos de ponteiros inteligentes que não mantêm uma contagem de referência. Por fim, as bibliotecas mencionadas são direcionadas a plataformas que possuem muitos recursos de sobra. Não que eu fosse o menos votado, mas tudo o que estou dizendo é que sua postagem está cheia de erros.
Filhote de cachorro

2
@ thb - eu concordo com o seu sentimento. DeadMG - Por favor, tente viver sem sistemas embarcados. Sim - alguns ponteiros inteligentes não têm sobrecarga, mas outros sim. O OP menciona bibliotecas. O Boost, por exemplo, possui peças usadas por sistemas incorporados - mas indicadores inteligentes podem ser inadequados para certos aplicativos.
Ed Heal #

2
@ EdHeal: Não viver sem sistemas embarcados! = Programar para eles não é uma minoria pequena e irrelevante. Ponteiros inteligentes são apropriados para todas as situações em que você precisa gerenciar a vida útil de um recurso.
Filhote de cachorro

4
shared_ptrnão tem sobrecarga. Ele só tem sobrecarga se você não precisar de semântica de propriedade compartilhada com segurança de thread, que é o que ela fornece.
R. Martinho Fernandes

1
Não, shared_ptr possui uma sobrecarga significativa em relação ao mínimo necessário para a semântica de propriedade compartilhada segura para threads; especificamente, ele aloca um bloco de heap separado do objeto real que você está compartilhando, com o único objetivo de armazenar a refcount. intrusive_ptr é mais eficiente, mas (como shared_ptr) também assume que todo ponteiro para o objeto será um intrusive_ptr. Você pode obter uma sobrecarga ainda menor do que o intrusive_ptr com um ponteiro compartilhado ref-counting personalizado, como eu faço no meu aplicativo, e usar T * sempre que puder garantir que pelo menos um ponteiro inteligente sobreviva ao valor T *.
Qwertie

2

Existem também outros tipos de ponteiros inteligentes. Você pode querer um ponteiro inteligente especializado para algo como replicação de rede (que detecta se foi acessada e envia quaisquer modificações ao servidor ou algo parecido), mantém um histórico de alterações, marca o fato de que foi acessado para que possa ser investigado quando você salva dados no disco e assim por diante. Não tenho certeza se fazer isso no ponteiro é a melhor solução, mas usar os tipos de ponteiro inteligente embutidos nas bibliotecas pode resultar em pessoas bloqueadas nelas e perder a flexibilidade.

As pessoas podem ter todos os tipos de requisitos e soluções de gerenciamento de memória diferentes, além de indicadores inteligentes. Talvez eu queira gerenciar a memória pessoalmente, alocando espaço para as coisas em um pool de memória, para que seja alocado antecipadamente e não em tempo de execução (útil para jogos). Talvez eu esteja usando uma implementação de C ++ coletada por lixo (o C ++ 11 torna isso possível, embora ainda não exista). Ou talvez eu não esteja fazendo nada avançado o suficiente para me preocupar em incomodá-los, posso saber que não vou esquecer de objetos não inicializados e assim por diante. Talvez eu esteja confiante na minha capacidade de gerenciar a memória sem a muleta do ponteiro.

A integração com C também é outra questão.

Outra questão é que ponteiros inteligentes fazem parte do STL. O C ++ foi projetado para ser utilizável sem o STL.


" Outra questão é que ponteiros inteligentes fazem parte do STL. " Eles não são.
Curiousguy

0

Também depende do domínio em que você trabalha. Eu escrevo mecanismos de jogo para viver, evitamos o impulso como uma praga, em jogos a sobrecarga do impulso não é aceitável. Em nosso mecanismo principal, acabamos escrevendo nossa própria versão do stl (bem como a ea stl).

Se eu fosse escrever um aplicativo de formulários, poderia considerar o uso de ponteiros inteligentes; mas quando o gerenciamento de memória é uma segunda natureza, não ter controle granular sobre a memória torna-se silenciosamente irritante.


3
Não existe algo como "sobrecarga de impulso".
Curiousguy

4
Eu nunca tinha compartilhado_ptr desacelerar meu mecanismo de jogo em um grau notável. Eles aceleraram o processo de produção e depuração. Além disso, o que exatamente você quer dizer com "a sobrecarga do impulso"? É um cobertor bem grande para lançar.
Derpface

@curiousguy: É a sobrecarga compilação de todos os cabeçalhos e voodoo macro + template ...
einpoklum
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.