Estou tendo problemas para entender o uso de ponteiros inteligentes como membros da classe em C ++ 11. Eu li muito sobre ponteiros inteligentes e acho que entendo como unique_ptr
e shared_ptr
/ weak_ptr
trabalho em geral. O que eu não entendo é o uso real. Parece que todo mundo recomenda usar unique_ptr
o caminho a percorrer quase o tempo todo. Mas como eu implementaria algo assim:
class Device {
};
class Settings {
Device *device;
public:
Settings(Device *device) {
this->device = device;
}
Device *getDevice() {
return device;
}
};
int main() {
Device *device = new Device();
Settings settings(device);
// ...
Device *myDevice = settings.getDevice();
// do something with myDevice...
}
Digamos que eu gostaria de substituir os ponteiros por ponteiros inteligentes. A unique_ptr
não funcionaria por causa de getDevice()
, certo? Então é nessa hora que eu uso shared_ptr
e weak_ptr
? Não tem como usar unique_ptr
? Parece-me que, na maioria dos casos, shared_ptr
faz mais sentido, a menos que eu esteja usando um ponteiro em um escopo muito pequeno?
class Device {
};
class Settings {
std::shared_ptr<Device> device;
public:
Settings(std::shared_ptr<Device> device) {
this->device = device;
}
std::weak_ptr<Device> getDevice() {
return device;
}
};
int main() {
std::shared_ptr<Device> device(new Device());
Settings settings(device);
// ...
std::weak_ptr<Device> myDevice = settings.getDevice();
// do something with myDevice...
}
Aquele é o caminho para ir? Muito obrigado!
fonte
device
para o construtor desettings
, você ainda pode se referir a ele no escopo da chamada ou apenas viasettings
? Se o último,unique_ptr
é útil. Além disso, você tem um cenário em que o valor de retornogetDevice()
énull
. Caso contrário, basta retornar uma referência.shared_ptr
está correto em 8/10 casos. Os outros 2/10 são divididos entreunique_ptr
eweak_ptr
. Além disso,weak_ptr
é geralmente usado para quebrar referências circulares; Não tenho certeza de que seu uso seria considerado correto.device
membro de dados? Você primeiro tem que decidir isso.unique_ptr
e renunciar à propriedade ao chamar o construtor, se eu souber que não precisarei mais dele por enquanto. Mas, como designer daSettings
classe, não sei se o chamador também quer manter uma referência. Talvez o dispositivo seja usado em muitos lugares. Ok, talvez esse seja exatamente o seu ponto. Nesse caso, eu não seria o único proprietário e é aí que eu usaria o shared_ptr, eu acho. E: pontos inteligentes substituem ponteiros, mas não referências, certo?Respostas:
Não, não necessariamente. O importante aqui é determinar a política de propriedade apropriada para o seu
Device
objeto, ou seja, quem será o proprietário do objeto apontado pelo seu ponteiro (inteligente).Será a instância do
Settings
objeto sozinha ? ODevice
objeto terá que ser destruído automaticamente quando oSettings
objeto for destruído ou deve sobreviver a ele?No primeiro caso,
std::unique_ptr
é o que você precisa, pois ele tornaSettings
o único (único) proprietário do objeto apontado e o único objeto responsável por sua destruição.Sob essa suposição,
getDevice()
deve retornar um ponteiro de observação simples (observar ponteiros são ponteiros que não mantêm o objeto apontado vivo). O tipo mais simples de observação de ponteiro é um ponteiro bruto:[ NOTA 1: Você pode estar se perguntando por que estou usando ponteiros brutos aqui, quando todo mundo fica dizendo que os ponteiros brutos são ruins, inseguros e perigosos. Na verdade, esse é um aviso precioso, mas é importante colocá-lo no contexto correto: ponteiros brutos são ruins quando usados para executar o gerenciamento manual de memória , ou seja, alocar e desalocar objetos através de
new
edelete
. Quando usado puramente como um meio de obter semântica de referência e passar ponteiros não-proprietários, observando ponteiros, não há nada intrinsecamente perigoso em ponteiros brutos, exceto talvez pelo fato de que se deve tomar cuidado para não desreferenciar um ponteiro pendente. - NOTA FINAL 1 ][ NOTA 2: Como surgiu nos comentários, neste caso específico em que a propriedade é única e o objeto de propriedade sempre está presente (ou seja, o membro de dados interno
device
nunca estará presentenullptr
), a funçãogetDevice()
poderia (e talvez devesse) retornar uma referência em vez de um ponteiro. Embora isso seja verdade, eu decidi retornar um ponteiro bruto aqui, porque eu quis dizer que essa seria uma resposta curta que poderia ser generalizada para o caso em quedevice
poderia estarnullptr
e mostrar que os ponteiros não processados são bons, desde que não sejam usados para gerenciamento manual de memória. - NOTA FINAL 2 ]A situação é radicalmente diferente, é claro, se o seu
Settings
objeto não tiver a propriedade exclusiva do dispositivo. Este poderia ser o caso, por exemplo, se a destruição doSettings
objeto não implicasse também a destruição doDevice
objeto apontado .Isso é algo que somente você, como designer do seu programa, pode dizer; pelo exemplo que você fornece, é difícil para mim dizer se esse é o caso ou não.
Para ajudá-lo a descobrir, você pode se perguntar se existem outros objetos além dos
Settings
que têm o direito de manter oDevice
objeto vivo enquanto mantiverem um ponteiro para ele, em vez de serem apenas observadores passivos. Se esse for realmente o caso, você precisará de uma política de propriedade compartilhada , que é o questd::shared_ptr
oferece:Observe que
weak_ptr
é um ponteiro de observação , não um ponteiro proprietário - em outras palavras, ele não mantém o objeto apontado vivo se todos os outros ponteiros proprietários do objeto apontado ficarem fora do escopo.A vantagem de
weak_ptr
um ponteiro bruto comum é que você pode dizer com segurança seweak_ptr
está danificado ou não (por exemplo, se está apontando para um objeto válido ou se o objeto apontado originalmente foi destruído). Isso pode ser feito chamando aexpired()
função de membro noweak_ptr
objeto.fonte
weak_ptr
é sempre uma alternativa aos ponteiros de observação brutos. É mais seguro em um sentido, porque você pode verificar se está oscilando antes de desreferenciá-lo, mas também vem com alguma sobrecarga. Se você pode facilmente garantir que você não está indo para excluir a referência um ponteiro pendurado, então você deve estar bem com a observação ponteiros crusgetDevice()
retornar uma referência, não é? Portanto, o chamador não precisaria procurarnullptr
.auto myDevice = settings.getDevice()
irá criar uma nova instância do tipoDevice
chamadamyDevice
e copiá-la a partir daquela referenciada pela referência quegetDevice()
retorna. Se você quermyDevice
ser uma referência, precisa fazerauto& myDevice = settings.getDevice()
. Portanto, a menos que esteja faltando alguma coisa, estamos de volta à mesma situação que tivemos sem usarauto
.unique_ptr
a um cliente abre a possibilidade de o cliente sair dele, adquirindo propriedade e deixando um ponteiro nulo (exclusivo).const_cast
), eu pessoalmente não faria isso. Ele expõe um detalhe de implementação, ou seja, o fato de que a propriedade é única e realizada por meio de aunique_ptr
. Eu vejo as coisas desta maneira: se você deseja / precisa passar / devolver a propriedade, passe / retorne um ponteiro inteligente (unique_ptr
oushared_ptr
, dependendo do tipo de propriedade). Se você não quiser / precisar passar / retornar a propriedade, use umconst
ponteiro ou referência (adequadamente qualificado), principalmente dependendo se o argumento pode ser nulo ou não.week_ptr
é usado apenas para loops de referência. O gráfico de dependência deve ser um gráfico direcionado acíclico. Nos ponteiros compartilhados, existem 2 contagens de referência: 1 para seshared_ptr
1 para todos os ponteiros (shared_ptr
eweak_ptr
). Quando todos osshared_ptr
s são removidos, o ponteiro é excluído. Quando o ponteiro é necessárioweak_ptr
,lock
deve ser usado para obter o ponteiro, se ele existir.fonte
shared_ptr
? Você pode, por favor, explicar o porquê? Tanto quanto eu entendo,weak_ptr
não precisa ser contado porque simplesmente cria um novoshared_ptr
ao operar no objeto (se o objeto subjacente ainda existir).lock
. A versão boost também é segura para threads na contagem de referência (delete
é chamada apenas uma vez).weak_ptr::lock()
para saber se o objeto expirou, ele deve inspecionar o "bloco de controle" que contém a primeira contagem de referência e ponteiro para o objeto, para que o bloco de controle não seja destruído enquanto ainda houverweak_ptr
objetos em uso, portanto, o número deweak_ptr
objetos deve ser rastreado, que é o que a segunda contagem de referência faz. O objeto é destruído quando a primeira contagem de referência cai para zero, o bloco de controle é destruído quando a segunda contagem de referência cai para zero.