std :: shared_ptr como último recurso?

59

Eu estava apenas assistindo as transmissões "Going Native 2012" e notei a discussão sobre std::shared_ptr. Fiquei um pouco surpreso ao ouvir a visão um tanto negativa de Bjarne std::shared_ptre seu comentário de que ele deveria ser usado como "último recurso" quando a vida de um objeto é incerta (o que acredito, segundo ele, não deve ser o caso).

Alguém se importaria de explicar isso com um pouco mais de profundidade? Como podemos programar sem std::shared_ptre ainda gerenciar a vida útil dos objetos de maneira segura ?

ronag
fonte
8
Não está usando ponteiros? Ter um dono distinto do objeto, que gerencia a vida inteira?
Bo Persson
2
e os dados compartilhados explicitamente? É difícil não usar ponteiros. Também std :: shared_pointer faria o sujo "gerir vida" nesse caso
Kamil Klimek
6
Você já pensou em ouvir menos os conselhos apresentados e mais o argumento por trás deles? Ele explica muito bem o tipo de sistema no qual esse tipo de conselho funcionaria.
Nicol Bolas
@ NicolBolas: Eu escutei os conselhos e a discussão, mas obviamente eu não senti que entendi bem o suficiente.
ronag
A que horas ele diz "último recurso"? Observando o trecho aos 36 minutos em ( channel9.msdn.com/Events/GoingNative/GoingNative-2012/… ), ele diz que desconfia do uso de ponteiros, mas quer dizer ponteiros em geral, não apenas shared_ptr e unique_ptr, mas até mesmo ' ponteiro regular. Ele implica que os próprios objetos (e não ponteiros para objetos alocados com novos) devem ser preferidos. Foi o que você estava pensando mais tarde na apresentação?
Pharap

Respostas:

55

Se você puder evitar a propriedade compartilhada, seu aplicativo será mais simples e fácil de entender e, portanto, menos suscetível a erros introduzidos durante a manutenção. Modelos de propriedade complexos ou pouco claros tendem a levar a acoplamentos difíceis de seguir de diferentes partes do aplicativo através de um estado compartilhado que pode não ser facilmente rastreável.

Diante disso, é preferível usar objetos com duração de armazenamento automático e ter subobjetos de "valor". Caso contrário, unique_ptrpode ser uma boa alternativa em shared_ptrser - se não um último recurso - de alguma forma na lista de ferramentas desejáveis.

CB Bailey
fonte
5
+1 por apontar que o problema não é o techno em si (propriedade compartilhada), mas as dificuldades que ele apresenta para nós, meros seres humanos, que precisam decifrar o que está acontecendo.
Matthieu M.
No entanto, adotar essa abordagem limitará severamente a capacidade de um programador de aplicar padrões de programação de simultaneidade na maioria das classes OOP não triviais (devido à não copiabilidade). Esse problema foi levantado no "Going Native 2013".
Rwong
48

O mundo em que Bjarne vive é muito ... acadêmico, por falta de um termo melhor. Se o seu código puder ser projetado e estruturado de modo que os objetos tenham hierarquias relacionais muito deliberadas, de modo que os relacionamentos de propriedade sejam rígidos e inflexíveis, o código fluirá em uma direção (de alto nível para baixo), e os objetos só falarão com os de baixo nível. hierarquia, então você não encontrará muita necessidade shared_ptr. É algo que você usa nessas raras ocasiões em que alguém tem que quebrar as regras. Mas, caso contrário, você pode colocar tudo em vectors ou outras estruturas de dados que usam semântica de valor unique_ptres para coisas que você deve alocar individualmente.

Embora esse seja um ótimo mundo para se viver, não é o que você pode fazer o tempo todo. Se você não pode organizar seu código dessa maneira, porque o design do sistema que você está tentando criar significa que é impossível (ou apenas profundamente desagradável), você se encontrará cada vez mais necessitando de propriedade compartilhada de objetos. .

Nesse sistema, manter ponteiros nus não é exatamente perigoso, mas levanta questões. O melhor de tudo shared_ptré que ele fornece garantias sintáticas razoáveis sobre a vida útil do objeto. Pode ser quebrado? Claro. Mas as pessoas também podem const_castcoisas; cuidados básicos e alimentação de shared_ptrdevem fornecer qualidade de vida razoável para objetos alocados cuja propriedade deve ser compartilhada.

Depois, há weak_ptrs, que não podem ser usados ​​na ausência de a shared_ptr. Se o seu sistema estiver rigidamente estruturado, você poderá armazenar um ponteiro nu para algum objeto, seguro de que a estrutura do aplicativo garante que o objeto apontado sobreviverá a você. Você pode chamar uma função que retorne um ponteiro para algum valor interno ou externo (localize o objeto chamado X, por exemplo). No código estruturado adequadamente, essa função só estaria disponível se a vida útil do objeto exceder a sua; portanto, é bom armazenar o ponteiro nu no seu objeto.

Como essa rigidez nem sempre é possível em sistemas reais, você precisa de uma maneira de garantir razoavelmente a vida útil. Às vezes, você não precisa de propriedade total; às vezes, você só precisa saber quando o ponteiro é ruim ou bom. É aí que weak_ptrentra. Houve casos em que eu poderia ter usado um unique_ptror boost::scoped_ptr, mas tive que usar um shared_ptrporque eu precisava especificamente fornecer a alguém um ponteiro "volátil". Um ponteiro com vida útil indeterminada, e eles poderiam consultar quando esse ponteiro foi destruído.

Uma maneira segura de sobreviver quando o estado do mundo é indeterminado.

Isso poderia ter sido feito por alguma chamada de função para obter o ponteiro, em vez de via weak_ptr? Sim, mas isso poderia ser mais facilmente quebrado. Uma função que retorna um ponteiro nu não tem como sugerir sintaticamente que o usuário não faça algo como armazenar esse ponteiro a longo prazo. O retorno de um shared_ptrtambém torna muito fácil para alguém simplesmente armazená-lo e potencialmente prolongar a vida útil de um objeto. Retornar um weak_ptrno entanto sugere fortemente que armazenar o que shared_ptrvocê recebe locké uma ... idéia duvidosa. Isso não impedirá que você faça isso, mas nada no C ++ o impede de quebrar o código. weak_ptrfornece alguma resistência mínima ao fazer a coisa natural.

Agora, isso não quer dizer que shared_ptrnão possa ser usado em excesso ; certamente pode. Especialmente antes unique_ptr, havia muitos casos em que eu apenas usava um boost::shared_ptrporque precisava passar um ponteiro RAII ou colocá-lo em uma lista. Sem movimento semântica e unique_ptr, boost::shared_ptrera a única solução real.

E você pode usá-lo em locais onde é desnecessário. Como mencionado acima, a estrutura de código adequada pode eliminar a necessidade de alguns usos de shared_ptr. Mas se o seu sistema não puder ser estruturado como tal e ainda fizer o que for necessário, shared_ptrserá de uso significativo.

Nicol Bolas
fonte
4
+1: Veja, por exemplo, boost :: asio. Acho que a ideia se estende a muitas áreas; talvez você não saiba em tempo de compilação qual widget da interface do usuário ou chamada assíncrona é a última a abandonar um objeto e, com o shared_ptr, você não precisa saber. Obviamente, não se aplica a todas as situações, apenas mais uma ferramenta (muito útil) na caixa de ferramentas.
Guy Sirton
3
Um comentário um pouco tarde; shared_ptré ótimo para sistemas em que o c ++ se integra à linguagem de script como python. Usando boost::python, a contagem de referência no lado c ++ e python coopera muito; qualquer objeto do c ++ ainda pode ser mantido em python e não morrerá.
Eudoxos
11
Apenas para referência, meu entendimento não é o WebKit nem o Chromium shared_ptr. Ambos usam suas próprias implementações de intrusive_ptr. Eu só trazer isso à tona porque ambos são exemplos do mundo real de grandes aplicações escritas em C ++
gman
11
@ gman: Acho seu comentário muito enganador, já que a objeção de Stroustrup se shared_ptraplica igualmente a intrusive_ptr: ele está se opondo a todo o conceito de propriedade compartilhada, não a qualquer grafia específica do conceito. Assim, para os fins desta questão, esses são dois exemplos do mundo real de grandes aplicações que não usam shared_ptr. (E, além do mais, eles demonstram que shared_ptré útil mesmo quando ele não permite weak_ptr.)
ruach
11
FWIW, para contrariar a alegação de que Bjarne está vivendo no mundo acadêmico: em toda a minha carreira puramente industrial (que incluiu co-arquitetar uma bolsa de valores do G20 e apenas um MOG de 500 mil jogadores), só vi três casos em que realmente precisamos propriedade compartilhada. Estou 200% com Bjarne aqui.
No-Bugs Hare
37

Eu não acredito que já usei std::shared_ptr.

Na maioria das vezes, um objeto é associado a alguma coleção, à qual pertence durante toda a sua vida útil. Nesse caso, você pode simplesmente usar whatever_collection<o_type>or whatever_collection<std::unique_ptr<o_type>>, essa coleção sendo um membro de um objeto ou uma variável automática. Obviamente, se você não precisar de um número dinâmico de objetos, poderá usar apenas uma matriz automática de tamanho fixo.

Nenhuma iteração através da coleção ou qualquer outra operação no objeto requer uma função auxiliar para compartilhar a propriedade ... ela usa o objeto, depois retorna e o chamador garante que o objeto permaneça ativo durante toda a chamada . Esse é de longe o contrato mais usado entre o chamador e o chamado.


Nicol Bolas comentou que "se algum objeto se apega a um ponteiro nu e esse objeto morre ... oops". e "Os objetos precisam garantir que o objeto passe pela vida desse objeto. Somente isso shared_ptrpode ser feito".

Eu não compro esse argumento. Pelo menos não isso shared_ptrresolve esse problema. Sobre:

  • Se alguma tabela de hash se apegar a um objeto e o código de hash do objeto mudar ... oops.
  • Se alguma função estiver iterando um vetor e um elemento for inserido nesse vetor ... oops.

Como a coleta de lixo, o uso padrão de shared_ptrincentiva o programador a não pensar no contrato entre objetos ou entre a função e o chamador. É necessário pensar em pré-condições e pós-condições corretas, e a vida útil do objeto é apenas um pedacinho dessa torta maior.

Objetos não "morrem", algum pedaço de código os destrói. E jogar shared_ptro problema em vez de descobrir o contrato de chamada é uma segurança falsa.

Ben Voigt
fonte
17
@ronag: Eu suspeito que você começou a usá-lo onde um ponteiro bruto teria sido melhor, porque "ponteiros brutos são ruins". Mas indicadores brutos não são ruins . Apenas tornar o ponteiro proprietário de um objeto um ponteiro bruto é ruim, pois é necessário gerenciar manualmente a memória, o que não é trivial na presença de exceções. Mas usar ponteiros brutos como alças ou iteradores é bom.
Ben Voigt
4
@BenVoigt: Obviamente, a dificuldade de passar ponteiros nus é que você não conhece a vida útil dos objetos. Se algum objeto se apega a um ponteiro nu e esse objeto morre ... oops. Isso é exatamente o tipo de coisa shared_ptre weak_ptrforam projetados para evitar. Bjarne tenta viver em um mundo onde tudo tem uma vida agradável e explícita, e tudo é construído em torno disso. E se você pode construir esse mundo, ótimo. Mas não é assim que é no mundo real. Os objetos precisam garantir que o objeto passe pela vida desse objeto. Só shared_ptrpode fazer isso.
Nicol Bolas
5
@ NicolBolas: Isso é falsa segurança. Se o chamador de uma função não estiver fornecendo a garantia usual: "Este objeto não será tocado por nenhuma parte externa durante a chamada de função", ambos precisam concordar com o tipo de modificação externa permitida. shared_ptrapenas mitiga uma modificação externa específica, e nem mesmo a mais comum. E não é responsabilidade do objeto garantir que sua vida útil esteja correta, se o contrato de chamada de função especificar o contrário.
Ben Voigt
6
@ NicolBolas: Se uma função cria um objeto e o retorna por ponteiro, deve ser a unique_ptr, expressando que existe apenas um ponteiro para o objeto e ele possui propriedade.
Ben Voigt
6
@ Nicol: Se estiver procurando um ponteiro em alguma coleção, provavelmente deve usar qualquer tipo de ponteiro nessa coleção, ou um ponteiro bruto se a coleção tiver valores. Se estiver criando um objeto e o chamador desejar a shared_ptr, ele ainda deve retornar a unique_ptr. A conversão de unique_ptrpara shared_ptré fácil, mas o inverso é logicamente impossível.
Ben Voigt
16

Prefiro não pensar em termos absolutos (como "último recurso"), mas em relação ao domínio do problema.

O C ++ pode oferecer várias maneiras diferentes de gerenciar a vida útil. Alguns deles tentam re-conduzir os objetos de maneira acionada por pilha. Alguns outros tentam escapar dessa limitação. Alguns deles são "literais", outros são aproximações.

Na verdade você pode:

  1. use semântica de valor puro . Funciona para objetos relativamente pequenos, onde o importante é "valores" e não "identidades", onde você pode assumir que dois Persontendo o mesmo namesão a mesma pessoa (melhor: duas representações de uma mesma pessoa ). A vida útil é concedida pela pilha da máquina, o que não é importante para o programa (afinal, uma pessoa é seu nome , não importa o que a Personesteja carregando)
  2. use objetos alocados à pilha e referências ou ponteiros relacionados: permite polimorfismo e concede vida útil ao objeto. Não há necessidade de "ponteiros inteligentes", pois você garante que nenhum objeto possa ser "apontado" por estruturas que saem da pilha por mais tempo do que o objeto para o qual apontam (primeiro crie o objeto, depois as estruturas que se referem a ele).
  3. use objetos alocados de heap gerenciado por pilha : é isso que std :: vector e todos os contêineres fazem e wat std::unique_ptr(você pode pensar nisso como um vetor com tamanho 1). Novamente, você admite que o objeto começa a existir (e termina sua existência) antes (depois) da estrutura de dados a que se refere.

O ponto fraco desses métodos é que os tipos e quantidades de objetos não podem variar durante a execução de chamadas mais profundas no nível da pilha em relação ao local em que foram criadas. Todas essas técnicas "fracassam" em todas as situações em que a criação e a exclusão de objetos são consequências das atividades do usuário, de modo que o tipo de tempo de execução do objeto não é conhecido em tempo de compilação e pode haver estruturas excessivas referentes aos objetos O usuário está solicitando a remoção de uma chamada de função no nível da pilha mais profunda. Nesse caso, você deve:

  • introduzir alguma disciplina sobre o gerenciamento de objetos e estruturas de referência relacionadas ou ...
  • vá de alguma forma para o lado sombrio de "escape da vida baseada em pilha pura": o objeto deve sair independentemente das funções que os criaram. E deve sair ... até que sejam necessários .

O C ++ isteslf não possui nenhum mecanismo nativo para monitorar esse evento ( while(are_they_needed)); portanto, você precisa se aproximar de:

  1. usar propriedade compartilhada : a vida dos objetos está vinculada a um "contador de referência": funciona se a "propriedade" pode ser organizada hierarquicamente, falha onde existem loops de propriedade. É isso que o std :: shared_ptr faz. E weak_ptr pode ser usado para quebrar o loop. Isso funciona na maioria das vezes, mas falha no design de grandes dimensões, onde muitos designers trabalham em equipes diferentes e não há uma razão clara (algo proveniente de um requisito) sobre quem é o dono do que (o exemplo típico são cadeias de gostos duplos: é o anterior devido ao próximo referente ao anterior ou o próximo proprietário do anterior referente ao próximo? Em virtude de um requisito, as soluções são equivalentes e, em grandes projetos, você corre o risco de confundi-las)
  2. Use uma pilha de coleta de lixo : você simplesmente não se importa com a vida. Você executa o coletor de tempos em tempos e o que é inacessível é considerado "não mais necessário" e ... bem ... ahem ... destruído? finalizado? congeladas?. Existem vários coletores de GC, mas nunca encontro um que seja realmente ciente de C ++. A maioria deles libera memória, sem se importar com a destruição de objetos.
  3. Use um coletor de lixo compatível com C ++ , com uma interface de métodos padrão adequada. Boa sorte para encontrá-lo.

Indo para a primeira solução até a última, a quantidade de estrutura de dados auxiliar necessária para gerenciar a vida útil do objeto aumenta, conforme o tempo gasto para organizá-lo e mantê-lo.

O coletor de lixo tem custo, shared_ptr tem menos, unique_ptr ainda menos e os objetos gerenciados por pilha têm muito poucos.

É shared_ptro "último recurso" ?. Não, não é: o último recurso são os coletores de lixo. shared_ptré realmente o std::último recurso proposto. Mas pode ser a solução certa, se você estiver na situação que expliquei.

Emilio Garavaglia
fonte
9

A única coisa mencionada por Herb Sutter em uma sessão posterior é que toda vez que você copia um, shared_ptr<>há um incremento / decremento intertravado que deve ocorrer. No código multiencadeado em um sistema multinúcleo, a sincronização de memória não é insignificante. Dada a escolha, é melhor usar um valor de pilha ou a unique_ptr<>e passar referências ou ponteiros brutos.

Eclipse
fonte
11
Ou passar shared_ptrpor lvalue ou referência rvalue ...
ronag
8
O ponto é que não basta usar shared_ptrcomo se fosse a bala de prata que resolverá todos os seus problemas de vazamento de memória apenas porque está no padrão. É uma armadilha tentadora, mas ainda é importante estar ciente da propriedade dos recursos e, a menos que essa propriedade seja compartilhada, a shared_ptr<>não é a melhor opção.
Eclipse
Para mim, esse é o detalhe menos importante. Veja otimização prematura. Na maioria dos casos, isso não deve conduzir a decisão.
Guy Sirton 04/02
11
@gbjbaanb: sim, eles estão no nível da CPU, mas em um sistema multinúcleo você está invalidando caches e forçando barreiras de memória.
Eclipse
4
Em um projeto de jogo em que trabalhei, descobrimos que a diferença de desempenho era muito significativa, a ponto de precisarmos de dois tipos diferentes de ponteiros ref-counted, um que era seguro para threads e outro que não era.
Kylotan
7

Não me lembro se o último "recurso" foi a palavra exata que ele usou, mas acredito que o significado real do que ele disse era a última "escolha": dadas condições claras de propriedade; unique_ptr, fraco_ptr, shared_ptr e até ponteiros nus têm seu lugar.

Uma coisa em que todos concordaram é que estamos (desenvolvedores, autores de livros etc.) todos na "fase de aprendizado" do C ++ 11 e padrões e estilos estão sendo definidos.

Como exemplo, Herb explicou que devemos esperar novas edições de alguns livros seminais em C ++, como Effective C ++ (Meyers) e C ++ Coding Standards (Sutter & Alexandrescu), daqui a alguns anos, enquanto a experiência e as melhores práticas do setor em C ++ 11 pan fora.

Eddie Velasquez
fonte
5

Eu acho que ele está entendendo que está se tornando comum que todos escrevam shared_ptr sempre que possam ter escrito um ponteiro padrão (como uma espécie de substituição global), e que ele está sendo usado como um cop-out em vez de realmente projetar ou pelo menos planejamento para criação e exclusão de objetos.

A outra coisa que as pessoas esquecem (além do gargalo de bloqueio / atualização / desbloqueio mencionado no material acima) é que o shared_ptr sozinho não resolve problemas de ciclo. Você ainda pode vazar recursos com shared_ptr:

O Objeto A, contém um ponteiro compartilhado para outro Objeto A O Objeto B cria A a1 e A a2 e atribui a a1.otherA = a2; e a2.outroA = a1; Agora, os ponteiros compartilhados do objeto B usados ​​para criar a1, a2 ficam fora do escopo (digamos, no final de uma função). Agora você tem um vazamento - ninguém mais se refere a a1 e a2, mas eles se referem um ao outro, de modo que suas contagens de referências são sempre 1 e você vazou.

Esse é o exemplo simples, quando isso ocorre em código real, geralmente acontece de maneiras complicadas. Existe uma solução com o fraco_ptr, mas agora muitas pessoas fazem o shared_ptr em todos os lugares e nem sequer sabem do problema de vazamento ou mesmo do fraco_ptr.

Para finalizar: acho que os comentários referenciados pelo OP se resumem a isso:

Independentemente do idioma em que você está trabalhando (gerenciado, não gerenciado ou algo intermediário com contagens de referência como shared_ptr), você precisa entender e decidir intencionalmente a criação, a duração e a destruição de objetos.

edit: mesmo que isso signifique "desconhecido, preciso usar um shared_ptr", você ainda pensou nisso e está fazendo isso intencionalmente.

anon
fonte
3

Responderei da minha experiência com o Objective-C, uma linguagem em que todos os objetos são contados e alocados na pilha de referência. Por ter uma maneira de tratar objetos, as coisas são muito mais fáceis para o programador. Isso permitiu a definição de regras padrão que, quando aderidas, garantem robustez do código e nenhum vazamento de memória. Também possibilitou a otimização inteligente do compilador, como o recente ARC (contagem automática de referência).

O que quero dizer é que shared_ptr deve ser sua primeira opção e não o último recurso. Use a contagem de referência por padrão e outras opções apenas se tiver certeza do que está fazendo. Você será mais produtivo e seu código será mais robusto.

Dimitris
fonte
1

Vou tentar responder à pergunta:

Como podemos programar sem std :: shared_ptr e ainda gerenciar a vida útil do objeto de maneira segura?

O C ++ possui um grande número de maneiras diferentes de executar a memória, por exemplo:

  1. Use em struct A { MyStruct s1,s2; };vez de shared_ptr no escopo da classe. Isso é apenas para programadores avançados, pois requer que você entenda como as dependências funcionam e requer capacidade de controlar dependências o suficiente para restringi-las a uma árvore. A ordem das classes no arquivo de cabeçalho é um aspecto importante disso. Parece que esse uso já é comum nos tipos nativos do c ++, mas é usado com classes definidas pelo programador, mas parece ser menos usado devido a esses problemas de dependência e ordem das classes. Esta solução também tem problemas com sizeof. Os programadores veem problemas nisso como um requisito para usar declarações futuras ou #includes desnecessários e, portanto, muitos programadores voltarão à solução inferior de ponteiros e posteriormente ao shared_ptr.
  2. Use MyClass &find_obj(int i);+ clone () em vez de shared_ptr<MyClass> create_obj(int i);. Muitos programadores desejam criar fábricas para criar novos objetos. shared_ptr é ideal para esse tipo de uso. O problema é que ele já assume uma solução complexa de gerenciamento de memória usando alocação de heap / free store, em vez de uma pilha mais simples ou uma solução baseada em objeto. Uma boa hierarquia de classes C ++ suporta todos os esquemas de gerenciamento de memória, não apenas um deles. A solução baseada em referência pode funcionar se o objeto retornado for armazenado dentro do objeto que contém, em vez de usar a variável de escopo da função local. A passagem da propriedade da fábrica para o código do usuário deve ser evitada. Copiar o objeto depois de usar o find_obj () é uma boa maneira de lidar com isso - os construtores de cópias normais e o construtor normal (de classe diferente) com o parâmetro de referência ou clone () para objetos polimórficos podem lidar com isso.
  3. Uso de referências em vez de ponteiros ou shared_ptrs. Toda classe c ++ possui construtores e cada membro de dados de referência precisa ser inicializado. Esse uso pode evitar muitos usos de ponteiros e shared_ptrs. Você só precisa escolher se sua memória está dentro ou fora do objeto e escolher a solução struct ou solução de referência com base na decisão. Os problemas com esta solução geralmente estão relacionados a evitar parâmetros do construtor, o que é uma prática comum, mas problemática, e a entender mal como as interfaces das classes devem ser projetadas.
tp1
fonte
"Passar a propriedade da fábrica para o código do usuário deve ser evitado." E o que acontece quando isso não é possível? "Uso de referências ao invés de ponteiros ou shared_ptrs." Hum, não. Os ponteiros podem ser recolocados. Referências não podem. Isso força restrições de tempo de construção sobre o que é armazenado em uma classe. Isso não é prático para muitas coisas. Sua solução parece ser muito rígida e inflexível para as necessidades de uma interface e um padrão de uso mais fluidos.
Nicol Bolas
@ Nicol Bolas: Depois de seguir as regras acima, as referências serão usadas para dependências entre objetos e não para armazenamento de dados, como você sugeriu. As dependências são mais estáveis ​​que os dados, portanto, nunca entramos no problema que você estava considerando.
tp1
Aqui está um exemplo muito simples. Você tem uma entidade de jogo, que é um objeto. Ele precisa se referir a outro objeto, que é uma entidade de destino com a qual precisa conversar. No entanto, os alvos podem mudar. Os alvos podem morrer em vários pontos. E a entidade precisa ser capaz de lidar com essas circunstâncias. Sua abordagem rígida sem ponteiros não consegue lidar com algo tão simples quanto mudar de alvo, sem falar no alvo que está morrendo.
Nicol Bolas
@nicol bolas: oh, isso é tratado de forma diferente; a interface da classe suporta mais de uma "entidade". Em vez do mapeamento 1: 1 entre objetos e entidades, você usará o array de entidade. Então, as entidades morrem com muita facilidade apenas removendo-a da matriz. Há apenas pequeno número de entityarrays em todo o jogo e dependências entre matrizes não mudam muito frequentemente :)
tp1
2
Não, unique_ptré mais adequado para fábricas. Você pode transformar um unique_ptrem um shared_ptr, mas é logicamente impossível seguir na outra direção.
precisa