Domanda

I stava sperimentando con shared_ptr e make_shared da C ++ 11 e programmato un piccolo esempio di giocattoli per vedere che cosa sta realmente accadendo al momento della chiamata make_shared. Mentre l'infrastruttura Stavo usando LLVM / clang 3.0, insieme alla libreria LLVM std C ++ all'interno XCode4.

class Object
{
public:
    Object(const string& str)
    {
        cout << "Constructor " << str << endl;
    }

    Object()
    {
        cout << "Default constructor" << endl;

    }

    ~Object()
    {
        cout << "Destructor" << endl;
    }

    Object(const Object& rhs)
    {
        cout << "Copy constructor..." << endl;
    }
};

void make_shared_example()
{
    cout << "Create smart_ptr using make_shared..." << endl;
    auto ptr_res1 = make_shared<Object>("make_shared");
    cout << "Create smart_ptr using make_shared: done." << endl;

    cout << "Create smart_ptr using new..." << endl;
    shared_ptr<Object> ptr_res2(new Object("new"));
    cout << "Create smart_ptr using new: done." << endl;
}

Ora dare un'occhiata in uscita, per favore:

Create smart_ptr make_shared ...

utilizzando

Constructor make_shared

Copia costruttore ...

Copia costruttore ...

Destructor

Destructor

Create smart_ptr utilizzando make_shared:. Fatto

Create smart_ptr utilizzando il nuovo ...

Constructor nuova

Create smart_ptr utilizzando il nuovo:. Fatto

Destructor

Destructor

Sembra che make_shared sta chiamando il costruttore di copia due volte. Se io allocare memoria per un Object utilizzando un new normale questo non succede, solo una Object è costruito.

Quello che mi chiedo è circa la seguente. Ho sentito che make_shared si suppone che sia più efficiente rispetto all'utilizzo di new ( 1 , 2 ) . Una ragione è che make_shared alloca il conteggio di riferimento insieme all'oggetto da gestire nello stesso blocco di memoria. OK, ho ottenuto il punto. Questo è ovviamente più efficiente di due operazioni di allocazione separata.

Al contrario non capisco il motivo per cui questo deve venire con il costo di due chiamate al costruttore di copia di Object. A causa di questo io non sono convinto che make_shared è più efficiente di allocazione utilizzando new in tutti caso. Mi sbaglio qui? Beh OK, si potrebbe implementare un costruttore mossa per Object, ma ancora non sono sicuro se questo è più efficiente di una semplice assegnazione Object attraverso new. A non meno importante in ogni caso. Sarebbe vero se la copia Object è meno costoso di allocazione di memoria per un contatore di riferimento. Ma il contatore di riferimento shared_ptr-interno potrebbe essere implementato usando un paio di tipi di dati primitivi, giusto?

possiamo aiutare e spiegare perché make_shared è la strada da percorrere in termini di efficienza, nonostante il sovraccarico copia delineato?

È stato utile?

Soluzione

Come infrastrutture stavo usando LLVM / clang 3.0, insieme alla libreria LLVM std C ++ all'interno XCode4.

Bene che sembra essere il problema. Il 11 stati standard i seguenti requisiti per make_shared<T> (e allocate_shared<T>) C ++, nella sezione 20.7.2.2.6:

Richiede: L'espressione :: nuovo (pv) T (std :: forward (args) ...), dove pv ha tipo * vuoto e punti di stoccaggio adatto a supportare un oggetto di tipo T, sono ben formati . A deve essere un allocatore (17.6.3.5). Il costruttore di copia e distruttore di A non devono generare eccezioni.

T è non richiesto per essere copia-costruibile. Infatti, T non è nemmeno richiesto di essere non-placement-nuova costruibile. E 'necessario solo per essere costruibile sul posto. Ciò significa che l'unica cosa che può fare con make_shared<T> T è new è sul posto.

Quindi, i risultati ottenuti non sono coerenti con lo standard. libc di LLVM ++ è rotto in questo senso. Segnalare un bug.

Per riferimento, ecco cosa è successo quando ho preso il codice in VC2010:

Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor
Destructor

Ho anche portato sotto shared_ptr originale di Boost e make_shared, e ho avuto la stessa cosa di VC2010.

suggerirei di segnalare un bug, come libc ++ 's comportamento è rotto.

Altri suggerimenti

Si deve confrontare queste due versioni:

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

Nel codice, la seconda variabile è solo un puntatore nudo, non un puntatore condiviso a tutti.


Ora sulla carne. make_shared (in pratica) più efficiente, perché alloca il blocco di controllo di riferimento insieme con l'oggetto reale in un'allocazione dinamica singolo. Al contrario, il costruttore di shared_ptr che accetta un puntatore nanometro oggetto deve allocare un'altra variabile dinamico per il conteggio di riferimento. Il trade-off è che make_shared (o il suo cugino allocate_shared) non consente di specificare un deleter personalizzato, in quanto l'assegnazione è eseguita dal allocatore.

(Questo non influisce la costruzione dell'oggetto stesso. Dal punto di vista Object non v'è alcuna differenza tra le due versioni. Cosa c'è di più efficace è il puntatore condiviso in sé, non l'oggetto gestito.)

Quindi, una cosa da tenere a mente è le impostazioni di ottimizzazione. Misurare le prestazioni, in particolare per c ++ è insignificante senza ottimizzazioni attivate. Non so se avete fatto in realtà di compilazione con le ottimizzazioni, così ho pensato che fosse degno di nota.

Detto questo, ciò che si sta misurando con questo test è non un modo che make_shared è più efficiente. In poche parole, si sta misurando la cosa :-P sbagliata.

Ecco il punto. Normalmente, quando si crea puntatore condiviso, ha almeno 2 membri di dati (forse di più). Uno per il puntatore, e uno per il conteggio dei riferimenti. Questo conteggio di riferimento è allocato sul heap (in modo che possa essere condiviso tra shared_ptr con diversi tempi di vita ... questo è il punto, dopo tutto!)

Quindi, se si sta creando un oggetto con qualcosa di simile std::shared_ptr<Object> p2(new Object("foo")); Ci sono almeno 2 le chiamate verso new. Uno per Object e uno per l'oggetto conteggio di riferimento.

make_shared ha l'opzione (io non sono sicuro che ha a), per fare un solo new che è abbastanza grande per contenere l'oggetto puntato e il conteggio dei riferimenti nello stesso blocco contiguo. Effettivamente l'assegnazione di un oggetto che sembra qualcosa di simile (illustrativo, non letteralmente quello che è).

struct T {
    int reference_count;
    Object object;
};

Dato che il contatore di riferimento e vite dell'oggetto sono legati insieme (che non ha senso per un vivere più lunga dell'altra). L'intero blocco può essere deleted allo stesso tempo pure.

Quindi, l'efficienza è in assegnazioni, non in copia (che ho il sospetto avuto a che fare con l'ottimizzazione di più di qualsiasi altra cosa).

Per essere chiari, questo è ciò che ha da dire spinta su circa make_shared

http://www.boost.org/doc/ libs / 1_43_0 / librerie / smart_ptr / make_shared.html

Oltre praticità e stile, tale funzione è anche sicuro un eccezione e notevolmente più veloce in quanto può utilizzare un unico stanziamento per sia l'oggetto e il suo corrispondente blocco di controllo, eliminando porzione significativa di overhead costruzione di shared_ptr. Questo elimina uno dei principali lamentele di efficienza di circa shared_ptr.

Si dovrebbe non essere sempre eventuali esemplari supplementari lì. L'output dovrebbe essere:

Create smart_ptr using make_shared...
Constructor make_shared
Create smart_ptr using make_shared: done.
Create smart_ptr using new...
Constructor new
Create smart_ptr using new: done.
Destructor

Non so il motivo per cui stai ricevendo copie extra. (Anche se vedo che stai ricevendo un 'distruttore' troppi, in modo che il codice è stato utilizzato per ottenere la vostra uscita deve essere diverso dal codice che hai postato)

make_shared è più efficiente perché può essere implementato utilizzando un solo allocazione dinamica invece di due, e poiché richiede una pena di puntatore di memoria inferiore contabilità per oggetto condiviso.

Modifica:. Non ho controllato con Xcode 4.2, ma con Xcode 4.3 ho l'uscita corretta mostro sopra, non l'uscita corretta mostrata nella domanda

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top