2012-03-12 19 views
6

Sto utilizzando boost :: make_shared per la prima volta per creare oggetti puntati da puntatori condivisi. Soprattutto perché il nostro codice era troppo lento e la singola allocazione ha davvero contribuito a migliorare le prestazioni.boost :: make_shared non sta chiamando l'operatore (posizionamento) nuovo?

Dopo aver risolto alcune perdite di memoria "il modo manuale difficile" ho deciso di implementare un rilevatore di perdite di memoria semplice sostituendo i nuovi operatori per tutte le classi rilevanti solo per contare quali oggetti sono ancora vivi in ​​punti specifici della nostra applicazione. L'ho già implementato più volte e sono rimasto sorpreso nel constatare che il mio codice non rileva più alcun oggetto.

Ho pensato che tutto quello che dovevo fare è override "nuova collocazione" al posto dell'operatore "normale" nuove di a causa dei seguenti dalla documentazione sito spinta per make_shared:

"Effetti: Alloca memoria adatto per un oggetto di tipo T e costruisce un oggetto tramite il posizionamento nuova espressione nuova (pv) T() o nuova (pv) T (std :: forward (args) ...). allocate_shared utilizza una copia di a per allocare memoria.Se viene generata un'eccezione, non ha l'effetto ".

Anche il mio inserimento nuovo non viene chiamato. Ho scritto un piccolo programma di test per riprodurre il comportamento:

#include <iostream> 
using namespace std; 
#include "boost/shared_ptr.hpp" 
#include "boost/make_shared.hpp" 

class Test 
{ 
public: 
    Test() { cout << "Test::Test()" << endl; } 

    void* operator new (std::size_t size) throw (std::bad_alloc) { 
     cout << "Test new" << endl; 
     return malloc(size); 
    } 

    void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw() { 
     cout << "Test non-throwing new" << endl; 
     return malloc(size); 
    } 

    void* operator new (std::size_t size, void* ptr) throw() { 
     cout << "Test non-throwing placement new" << endl; 
     return malloc(size); 
    } 
}; 

void* operator new (std::size_t size) throw (std::bad_alloc) { 
    cout << "Global new" << endl; 
    return malloc(size); 
} 

int main() { 
    cout << "..." << endl; 
    boost::shared_ptr<Test> t1(boost::make_shared<Test>()); 
    cout << "..." << endl; 
    boost::shared_ptr<Test> t2(new Test()); 
    cout << "..." << endl; 

    return 0; 
} 

che rende il seguente output:

... 
Global new 
Test::Test() 
... 
Test new 
Test::Test() 
Global new 
... 

mi aspettavo "Test non gettare nuova collocazione" al 3 ° riga di output. Cosa pensi che dovrebbe essere il comportamento? Sei d'accordo che secondo la documentazione di make_shared dovrebbe chiamare il nuovo operatore della mia classe di test? O l'ho frainteso?

Potrei copiare localmente l'implementazione di boost e aggiungere una chiamata al nuovo operatore di posizionamento, ovviamente. Ma sarebbe appropriato o violerebbe la semantica del collocamento nuovo?

Grazie in anticipo per il vostro tempo e il vostro aiuto.

+0

Osservando boost , sta utilizzando il nuovo posizionamento globale operator :: new (pv) T(). Ecco perché il posizionamento a livello di classe non viene chiamato ... Rimuovendo il qualificatore globale '::' prima del nuovo, make_shared chiama effettivamente il nuovo operatore di posizionamento a livello di classe. – Gob00st

risposta

8

In qualità di sorgente di make_shared, utilizza l'operatore di posizionamento globale new, anziché il nuovo operatore fornito dalla classe.

::new(pv) T(); 

Purtroppo (come almeno su OS X) (according to the standard), non è possibile definire il proprio posizionamento globale nuovo operatore. Sembra che allocate_shared sia più simile a quello che stai cercando.

Edit:

Un'alternativa potrebbe essere quella di scrivere in realtà una versione di make_shared che utilizza il posizionamento della classe nuova, invece di quella globale. Sono solo circa 10 righe di codice, e dovrebbero andare bene fino a quando rispondi allo the license of the original code.

+0

Vale la pena notare che 'allocate_shared()' potrebbe significare sostituire alcune chiamate in una base di codice più grande. –

+0

Grazie per la risposta utile. Non sapevo che non avrei dovuto scavalcare il posizionamento nuovo. Quindi questo è sicuramente un ostacolo per il mio tentativo di soluzione. Ho già studiato l'opzione allocate_shared, ma, come Georg ha dichiarato, questo ha un impatto troppo elevato sul codice esistente. –

4

Non è possibile sostituire il nuovo inserimento (§18.4 1.3, vedere ad esempio this question), quindi l'output indicato sembra soddisfacente.

In alternativa alla modifica delle intestazioni di Boost, è possibile esaminare strumenti esterni come Valgrind.

3

vostro operator new implementata per il tipo particolare saranno utilizzati solo su espressioni sui quali elementi del vostro tipo è allocati dinamicamente con new, come Test *p = new Test;. Ora make_shared non fa in modo dinamico alloca un oggetto del vostro tipo, ma piuttosto un tampone che contiene informazioni sufficienti per il conteggio condiviso (che comprende il contatore, deleter e alcuni pezzi supplementari) e l'oggetto.

Quindi utilizza posizionamento-nuovo per chiamare il costruttore del proprio oggetto. Notare che il posizionamento nuovo in questo caso non alloca memoria, è solo la sintassi divertente in C++ per chiamare il costruttore su un blocco di memoria già allocata. Questo potrebbe effettivamente essere fonte di confusione, poiché l'espressione new, il tuo operator new e posizionamento-nuovo sono tre concetti diversi che capita di condividere un nome.

+0

Nonostante ciò, è ancora possibile fornire un sovraccarico per classe anche per il posizionamento nuovo. Forse inusuale, ma ancora una volta, quale parte del C++ * non è * insolita :-) Il punto principale da portare a casa è che 'std :: allocator' non usa alcuna funzione di allocazione per classe, ma solo il globale' :: new'. –