2016-05-17 38 views
15

Così quando si utilizza shared_ptr<Type> si può scrivere:Perché non shared_ptr permettere l'assegnazione diretta

shared_ptr<Type> var(new Type()); 

Mi chiedo perché non hanno permesso un molto più semplice e migliore (IMO):

shared_ptr<Type> var = new Type(); 

Invece di raggiungere tale funzionalità è necessario utilizzare .reset():

shared_ptr<Type> var; 
var.reset(new Type()); 

sono abituato a Classe OpenCV Ptr che è un puntatore intelligente che consente l'assegnazione diretta e tutto funziona correttamente

+3

Poiché il costruttore di 'std :: shared_ptr' che prende il puntatore è' esplicito' e non c'è alcun operatore che prende il puntatore. – Jarod42

+9

Non è un compito. – LogicStuff

risposta

20

Il problema con che permette un puntatore prima da implicitamente convertito in un std::shared_ptr può essere dimostrata con

void foo(std::shared_ptr<int> bar) { /*do something, doesn't matter what*/ } 

int main() 
{ 
    int * bar = new int(10); 
    foo(bar); 
    std::cout << *bar; 
} 

Ora, se il conversione implicita ha funzionato la memoria bar punti per essere eliminato dal distruttore shared_ptr alla fine del foo(). Quando andiamo ad accedervi in ​​std::cout << *bar; abbiamo ora un comportamento indefinito mentre stiamo dereferenziando un puntatore cancellato.

Nel tuo caso, puoi creare il puntatore direttamente nel sito di chiamata in modo che non importi, ma come puoi vedere dall'esempio può causare problemi.

8

Perché [non] shared_ptr consente l'assegnazione diretta [copia inizializzazione]?

Perché è explicit, vedi here e here.

Mi chiedo quale sia la logica [dietro] dietro di esso? (Da un commento ora rimosso)

TL; DR, fare qualsiasi costruttore (o fusione) explicit è quello impedirà partecipano in sequenze conversione implicita.

Il requisito per explicit è illustrato meglio con shared_ptr<> è un argomento per una funzione.

void func(std::shared_ptr<Type> arg) 
{ 
    //... 
} 

E chiamato come;

Type a; 
func(&a); 

Questa sarebbe la compilazione, e come scritto ed è indesiderato e sbagliato; non si comporterà come previsto.

Si complica ulteriormente con l'aggiunta di conversioni (implicite) definite dall'utente (operatori di casting) nel mix.

struct Type { 
}; 

struct Type2 { 
    operator Type*() const { return nullptr; } 
}; 

Poi la seguente funzione (se non esplicito) sarebbe compilare, ma offre un orribile insetto ...

Type2 a; 
func(a); 
16

Permettendo questo permette di chiamare funzioni con argomenti puntatore direttamente, che è soggetto a errori perché non sei necessariamente a conoscenza del sito di chiamata che stai creando un puntatore condiviso da esso.

void f(std::shared_ptr<int> arg); 
int a; 
f(&a); // bug 

Anche se si ignora questo, si crea la temporanea invisibile al luogo di chiamata, e la creazione di shared_ptr è piuttosto costoso.

+1

A meno che tu non lo crei con 'new' perché in questo modo stai' eliminando la memoria non hai 'new'ed. – milleniumbug

+0

@ giò, può causare un'ambiguità di sovraccarico e fare un 'shared_ptr' è più costoso di quanto la maggior parte della gente vorrebbe se fosse implicita. Per altre classi, può anche essere una trasformazione non ovvia. Ad esempio, passare '5' e averlo trasformato in' std :: vector' sarebbe molto intuitivo. – chris

28

La sintassi:

shared_ptr<Type> var = new Type(); 

Is copy initialization. Questo è il tipo di inizializzazione usato per gli argomenti della funzione.

Se consentito, è possibile passare accidentalmente un puntatore semplice a una funzione utilizzando un puntatore intelligente. Inoltre, se durante la manutenzione, qualcuno ha cambiato void foo(P*) in void foo(std::shared_ptr<P>) che si compilerebbe altrettanto bene, con conseguente comportamento non definito.

Poiché questa operazione si basa essenzialmente sulla proprietà di un puntatore semplice, questa operazione deve essere eseguita in modo esplicito. Questo è il motivo per cui il costruttore shared_ptr che accetta un puntatore semplice viene creato explicit - per evitare conversioni implicite accidentali.


L'alternativa più sicura e più efficiente è:

auto var = std::make_shared<Type>(); 
+4

@ giò E 'davvero un grosso problema. –

+1

@ giò Un puntatore condiviso presuppone che sia proprietario del puntatore (e lo condivida solo con altri puntatori condivisi).Se hai passato un puntatore raw a un puntatore condiviso, è probabile che un altro pezzo di codice assuma che sia proprietario del puntatore raw e proverà a eliminarlo a un certo punto. Ma così sarebbe il puntatore condiviso! Quindi sì, la doppia eliminazione non è affatto buona. – KABoissonneault

8

Mi chiedo perché non hanno permesso un molto più semplice e meglio ...

tua opinione cambierà come si diventa più esperti e incontrare più scritto male, il codice buggy.

shared_ptr<>, come tutti gli oggetti di libreria standard sono scritti in modo tale da rendere il più difficile possibile causare comportamenti non definiti (vale a dire difficili da trovare bug che sprecano il tempo di tutti e distruggono la nostra volontà di vivere).

considerare:

#include<memory> 

struct Foo {}; 

void do_something(std::shared_ptr<Foo> pfoo) 
{ 
    // ... some things 
} 

int main() 
{ 
    auto p = std::make_shared<Foo>(/* args */); 
    do_something(p.get()); 
    p.reset(); // BOOM! 
} 

Questo codice non può compilare, e che è una buona cosa. Perché se lo facesse, il programma mostrerebbe un comportamento indefinito.

Questo perché elimineremmo lo stesso Foo due volte.

Questo programma verrà compilato ed è ben formato.

#include<memory> 

struct Foo {}; 

void do_something(std::shared_ptr<Foo> pfoo) 
{ 
    // ... some things 
} 

int main() 
{ 
    auto p = std::make_shared<Foo>(/* args */); 
    do_something(p); 
    p.reset(); // OK 
}