Perché le librerie e i framework C ++ non usano mai i puntatori intelligenti?


156

Ho letto in alcuni articoli che i puntatori grezzi non dovrebbero quasi mai essere usati. Invece dovrebbero sempre essere racchiusi all'interno di puntatori intelligenti, che siano puntatori con ambito o condivisi.

Tuttavia, ho notato che framework come Qt, wxWidgets e librerie come Boost non ritornano mai né si aspettano puntatori intelligenti, come se non li usassero affatto. Al contrario, restituiscono o si aspettano puntatori non elaborati. C'è qualche motivo per quello? Devo stare lontano dai suggerimenti intelligenti quando scrivo un'API pubblica e perché?

Mi chiedo solo perché i consigli intelligenti sono raccomandati quando molti grandi progetti sembrano evitarli.


22
Tutte quelle librerie che hai appena nominato sono state avviate molti anni fa. I puntatori intelligenti sono diventati veramente standard solo in C ++ 11.
chrisaycock,

22
i puntatori intelligenti hanno un overhead (conteggio dei riferimenti, ecc.) - che può essere critico - per esempio nei sistemi embedded / real time. IMHO - i puntatori intelligenti sono per programmatori pigri. Inoltre, molte API scelgono il minimo comune denominatore. Sento le fiamme che mi leccano i piedi mentre scrivo!
Ed Heal,

93
@EdHeal: Il motivo per cui puoi sentire le fiamme leccare intorno ai tuoi piedi è perché ti sbagli completamente sotto ogni aspetto. Ad esempio, in che cosa si trova l'overhead unique_ptr? Assolutamente no. Qt / WxWidgets sono destinati a sistemi embedded o in tempo reale? No, sono destinati al massimo per Windows / Mac / Unix su un desktop. I puntatori intelligenti sono per i programmatori che vogliono ottenerlo corretto.
Cucciolo

24
In realtà, i telefoni cellulari eseguono Java.
R. Martinho Fernandes,

12
I puntatori intelligenti sono veramente standard solo in C ++ 11? Che cosa??? Queste cose sono state usate per più di 20 anni.
Kaz,

Risposte:


124

A parte il fatto che molte librerie sono state scritte prima dell'avvento dei puntatori intelligenti standard, la ragione principale è probabilmente la mancanza di un'interfaccia binaria standard (ABI) per applicazioni C ++.

Se stai scrivendo una libreria solo intestazione, puoi passare puntatori intelligenti e contenitori standard al contenuto del tuo cuore. La loro fonte è disponibile per la tua libreria al momento della compilazione, quindi fai affidamento sulla stabilità delle loro interfacce da sola, non delle loro implementazioni.

Ma a causa della mancanza di ABI standard, generalmente non è possibile passare questi oggetti in modo sicuro oltre i confini del modulo. Un GCC shared_ptrè probabilmente diverso da un MSVC shared_ptr, che può anche differire da un Intel shared_ptr. Anche con lo stesso compilatore, queste classi non sono garantite per essere binarie compatibili tra le versioni.

La linea di fondo è che se si desidera distribuire una versione predefinita della propria libreria, è necessario un ABI standard su cui fare affidamento. C non ne ha uno, ma i produttori di compilatori sono molto bravi sull'interoperabilità tra le librerie C per una determinata piattaforma: esistono di fatto standard.

La situazione non è buona per C ++. I singoli compilatori possono gestire l'interoperabilità tra i propri binari, quindi hai la possibilità di distribuire una versione per ogni compilatore supportato, spesso GCC e MSVC. Alla luce di ciò, la maggior parte delle librerie esporta semplicemente un'interfaccia C, e ciò significa puntatori non elaborati.

Il codice non di libreria dovrebbe, tuttavia, preferire generalmente puntatori intelligenti rispetto a raw.


17
Sono d'accordo con te, anche passare una stringa std :: string può essere una seccatura, questo dice molto sul C ++ come "ottimo linguaggio per le biblioteche".
Ha tenuto il

8
La linea di fondo è più simile: se vuoi distribuire una versione pre-build, devi farlo per ogni compilatore che vuoi supportare.
josefx,

6
@josefx: Sì, questo è triste ma vero, l'unica alternativa è COM o un'interfaccia C non elaborata. Vorrei che la comunità C ++ iniziasse a preoccuparsi per questo tipo di problemi. Voglio dire, non è che il C ++ sia un nuovo linguaggio di 2 anni fa.
Robot Mess

3
Ho annullato il voto perché questo è sbagliato. I problemi ABI sono più che gestibili nella maggior parte dei casi. Sebbene difficilmente user-friendly, ABI è anche difficilmente insormontabile.
Cucciolo

4
@NathanAdams: tale software è senza dubbio impressionante e utile. Ma tratta il sintomo di problemi più profondi: la semantica C ++ della vita e della proprietà sono da qualche parte tra impoveriti e inesistenti. Quei bug di heap non sarebbero sorti se la lingua non li avesse consentiti. Quindi, i puntatori intelligenti non sono una panacea: sono un tentativo di recuperare alcune delle perdite subite utilizzando in primo luogo il C ++.
Jon Purdy,

40

Ci possono essere molte ragioni. Per elencarne alcuni:

  1. Puntatori intelligenti sono diventati parte dello standard solo di recente. Fino ad allora facevano parte di altre biblioteche
  2. Il loro uso principale è quello di evitare perdite di memoria; molte librerie non hanno una propria gestione della memoria; Generalmente forniscono utilità e API
  3. Sono implementati come wrapper, poiché in realtà sono oggetti e non puntatori. Che ha costi aggiuntivi di tempo / spazio, rispetto ai puntatori non elaborati; Gli utenti delle librerie potrebbero non voler avere tali costi generali

Modifica : l'utilizzo dei puntatori intelligenti è una scelta completamente sviluppata dagli sviluppatori. Dipende da vari fattori.

  1. Nei sistemi critici per le prestazioni, potresti non voler utilizzare i puntatori intelligenti che generano sovraccarico

  2. Nel progetto che richiede la compatibilità con le versioni precedenti, potresti non voler utilizzare i puntatori intelligenti con caratteristiche specifiche di C ++ 11

Edit2 Esiste una serie di più downvotes nell'arco di 24 ore a causa del passaggio sottostante. Non riesco a capire perché la risposta sia stata sottoposta a downgrade anche se di seguito è solo un suggerimento aggiuntivo e non una risposta.
Tuttavia, C ++ ti aiuta sempre ad avere le opzioni aperte. :) per esempio

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

E nel tuo codice usalo come:

Pointer<int>::type p;

Per coloro che affermano che un puntatore intelligente e un puntatore non elaborato sono diversi, sono d'accordo. Il codice sopra era solo un'idea in cui si può scrivere un codice che è intercambiabile solo con un #define, questa non è una coazione ;

Ad esempio, T*deve essere eliminato esplicitamente ma non un puntatore intelligente. Possiamo avere un modello Destroy()per gestirlo.

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

e usalo come:

Destroy(p);

Allo stesso modo, per un puntatore non elaborato possiamo copiarlo direttamente e per un puntatore intelligente possiamo utilizzare operazioni speciali.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Dov'è Assign()come:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}

14
Su 3. Alcuni puntatori intelligenti hanno costi aggiuntivi di tempo / spazio, altri no, incluso std::auto_ptrquello che fa parte dello standard da molto tempo (e nota, mi piace std::auto_ptrcome tipo di ritorno per le funzioni che creano oggetti, anche se lo è quasi inutile altrove). In C ++ 11 std::unique_ptrnon ha costi aggiuntivi rispetto a un semplice puntatore.
David Rodríguez - dribeas,

4
Esattamente ... c'è una bella simmetria sull'aspetto unique_ptre la scomparsa di auto_ptr, il targeting per codice C ++ 03 dovrebbe usare il successivo, mentre il targeting per codice C ++ 11 può usare il primo. I puntatori intelligenti non lo sono shared_ptr, ci sono molti standard e nessuno standard, comprese le proposte allo standard che sono state respinte in quantomanaged_ptr
David Rodríguez - dribeas,

2
@iammilind, questi sono punti interessanti, ma la cosa divertente è che se finiamo per usare i puntatori intelligenti, come apparentemente molti consiglierebbero, finiremo per creare codice incompatibile con le principali librerie. Naturalmente, possiamo avvolgere / scartare i puntatori intelligenti secondo necessità, ma sembra un sacco di seccatura e creerebbe un codice incoerente (a volte ci occupiamo di puntatori intelligenti, a volte no).
laurent

7
L'affermazione secondo cui i puntatori intelligenti hanno "costi tempo / spazio aggiuntivi" è in qualche modo fuorviante; tutti i puntatori intelligenti ad eccezione dei unique_ptrcosti di runtime, ma unique_ptrè di gran lunga quello più comunemente usato. Anche l'esempio di codice fornito è fuorviante, perché unique_ptre T*sono concetti completamente diversi. Il fatto che ci si riferisca a entrambi come se typeavesse l'impressione che possano essere scambiati l'uno con l'altro.
void-pointer

12
Non è possibile digitare in questo modo, questi tipi non sono in alcun modo equivalenti. Scrivere dattiloscritti come questo richiede problemi.
Alex B,

35

Esistono due problemi con i puntatori intelligenti (pre C ++ 11):

  • non standard, quindi ogni libreria tende a reinventare la propria (problemi di NIH syndrom e dipendenze)
  • costo potenziale

Il puntatore intelligente predefinito , in quanto è gratuito, è unique_ptr. Sfortunatamente richiede la semantica di spostamento in C ++ 11, che è apparsa solo di recente. Tutti gli altri puntatori intelligenti hanno un costo ( shared_ptr, intrusive_ptr) o hanno una semantica meno che ideale ( auto_ptr).

Con C ++ 11 dietro l'angolo, portando un std::unique_ptr, si sarebbe tentati di pensare che finalmente è finita ... Non sono così ottimista.

Solo alcuni compilatori importanti implementano la maggior parte di C ++ 11 e solo nelle loro versioni recenti. Possiamo aspettarci che le principali biblioteche come QT e Boost siano disposte a mantenere la compatibilità con C ++ 03 per un po ', il che preclude in qualche modo l'ampia adozione dei nuovi e brillanti puntatori intelligenti.


12

Non dovresti stare lontano dai puntatori intelligenti, hanno il loro uso soprattutto nelle applicazioni in cui devi passare un oggetto.

Le librerie tendono a restituire un valore o a popolare un oggetto. Di solito non hanno oggetti che devono essere utilizzati in molti luoghi, quindi non è necessario che utilizzino i puntatori intelligenti (almeno non nella loro interfaccia, potrebbero usarli internamente).

Potrei prendere ad esempio una libreria su cui stiamo lavorando, dove dopo alcuni mesi di sviluppo ho capito che in alcune classi abbiamo usato solo puntatori e puntatori intelligenti (3-5% di tutte le classi).

Passare variabili per riferimento era abbastanza nella maggior parte dei luoghi, usavamo i puntatori intelligenti ogni volta che avevamo un oggetto che poteva essere nullo e i puntatori grezzi quando una libreria che usavamo ci costringeva.

Modifica (non posso commentare a causa della mia reputazione): passare variabili per riferimento è molto flessibile: se vuoi che l'oggetto sia di sola lettura puoi usare un riferimento const (puoi ancora fare alcuni lanci cattivi per poter scrivere l'oggetto ) ma ottieni il massimo della protezione possibile (è lo stesso con i puntatori intelligenti). Ma sono d'accordo sul fatto che è molto più bello restituire l'oggetto.


Non sono in disaccordo, con te, esattamente, ma sottolineerò che esiste una scuola di pensiero che deprezza il passaggio di riferimenti variabili nella maggior parte dei casi. Confesso di aderire a quella scuola. Preferisco le funzioni per non modificare i loro argomenti. Ad ogni modo, per quanto ne so, i riferimenti alle variabili di C ++ non fanno nulla per impedire la cattiva gestione degli oggetti a cui si riferiscono, che è ciò che i puntatori intelligenti intendono fare.
thb,

2
hai const per quello (sembra che posso commentare: D).
Robot Mess

9

Qt ha reinventato inutilmente molte parti della libreria Standard nel tentativo di diventare Java. Credo che ora abbia effettivamente i suoi puntatori intelligenti, ma in generale, non è quasi un apice del design. wxWidgets, per quanto ne so, è stato progettato molto prima che venissero scritti i puntatori intelligenti utilizzabili.

Per quanto riguarda Boost, mi aspetto pienamente che utilizzino i puntatori intelligenti ovunque sia opportuno. Potrebbe essere necessario essere più specifici.

Inoltre, non dimenticare che esistono puntatori intelligenti per imporre la proprietà. Se l'API non ha una semantica di proprietà, perché usare un puntatore intelligente?


19
Qt è stato scritto prima che gran parte della funzionalità fosse sufficientemente diffusa sulle piattaforme che voleva utilizzare. Ha avuto puntatori intelligenti per molto tempo e li usa per fare la condivisione implicita di risorse in quasi tutte le classi Q *.
rubenvb,

6
Ogni libreria GUI reinventa inutilmente la ruota. Anche le stringhe, Qt ha QString, wxWidgets ha wxString, MFC ha il nome orribile CString. Un UTF-8 non è std::stringabbastanza buono per il 99% delle attività della GUI?
Inverso,

10
@Inverse QString è stato creato quando std :: string non c'era.
MrFox,

Controlla quando è stato creato qt e quali puntatori intelligenti erano disponibili in quel momento.
Dainius,

3

Buona domanda. Non conosco gli articoli specifici a cui ti riferisci, ma ho letto cose simili di volta in volta. Il mio sospetto è che gli autori di tali articoli tendano a nutrire un pregiudizio contro la programmazione in stile C ++. Se lo scrittore programma in C ++ solo quando deve, quindi ritorna a Java o simili appena può, non condivide davvero la mentalità C ++.

Si sospetta che alcuni o la maggior parte degli stessi scrittori preferiscano i gestori di memoria per la raccolta dei rifiuti. Non lo so, ma penso diversamente da loro.

I puntatori intelligenti sono fantastici, ma devono mantenere i conteggi dei riferimenti. Il mantenimento dei conteggi comporta costi - spesso costi modesti, ma comunque costi - in fase di esecuzione. Non c'è nulla di sbagliato nel salvare questi costi usando puntatori nudi, specialmente se i puntatori sono gestiti da distruttori.

Una delle cose eccellenti di C ++ è il suo supporto per la programmazione di sistemi embedded. L'uso di puntatori nudi è parte di questo.

Aggiornamento: un commentatore ha correttamente osservato che il nuovo C ++ unique_ptr(disponibile da TR1) non conta i riferimenti. Il commentatore ha anche una diversa definizione di "puntatore intelligente" rispetto a quello che ho in mente. Potrebbe avere ragione sulla definizione.

Ulteriore aggiornamento: il thread dei commenti qui sotto è illuminante. Si consiglia di leggere tutto.


2
Tanto per cominciare, la programmazione di sistemi embedded è una vasta minoranza di tutta la programmazione e abbastanza irrilevante. C ++ è un linguaggio generico. In secondo luogo, shared_ptrmantiene un conteggio dei riferimenti. Esistono molti altri tipi di puntatori intelligenti che non mantengono affatto un conteggio dei riferimenti. Infine, le biblioteche menzionate sono indirizzate a piattaforme che hanno molte risorse da risparmiare. Non che fossi il downvoter, ma tutto ciò che sto dicendo è che il tuo post è pieno di errori.
Cucciolo

2
@thb - Sono d'accordo con te sentimento. DeadMG - Prova a vivere senza sistemi integrati. Sì, alcuni puntatori intelligenti non hanno un sovraccarico, ma alcuni lo fanno. L'OP menziona le biblioteche. Boost, ad esempio, contiene parti utilizzate dai sistemi incorporati, ma i puntatori intelligenti potrebbero non essere appropriati per determinate applicazioni.
Ed Heal,

2
@EdHeal: non vivere senza sistemi embedded! = La programmazione per loro non è una piccola, irrilevante, minoranza. I puntatori intelligenti sono appropriati per ogni situazione in cui è necessario gestire la durata di una risorsa.
Cucciolo

4
shared_ptrnon ha spese generali. Ha un sovraccarico solo se non hai bisogno della semantica della proprietà condivisa thread-safe, che è ciò che fornisce.
R. Martinho Fernandes,

1
No, shared_ptr ha un overhead significativo rispetto al minimo necessario per la semantica della proprietà condivisa thread-safe; in particolare alloca un blocco heap separato dall'oggetto reale che si sta condividendo, al solo scopo di memorizzare il refcount. intrusive_ptr è più efficiente, ma (come shared_ptr) presuppone anche che ogni puntatore all'oggetto sia un intrusive_ptr. Puoi ottenere un overhead ancora più basso di intrusive_ptr con un puntatore condiviso di conteggio dei riferimenti personalizzato, come faccio nella mia app, e quindi utilizzare T * ogni volta che puoi garantire che almeno un puntatore intelligente sopravviverà al valore T *.
Qwertie,

2

Esistono anche altri tipi di puntatori intelligenti. Potresti desiderare un puntatore intelligente specializzato per qualcosa come la replica di rete (uno che rileva se è stato effettuato l'accesso e invia eventuali modifiche al server o qualcosa del genere), conserva una cronologia delle modifiche, segnala il fatto che è stato effettuato l'accesso in modo che possa essere esaminato quando si salvano i dati su disco e così via. Non sono sicuro che farlo nel puntatore sia la soluzione migliore, ma l'uso dei tipi di puntatore intelligente incorporato nelle librerie potrebbe comportare il blocco delle persone e la perdita della flessibilità.

Le persone possono avere tutti i tipi di diversi requisiti e soluzioni di gestione della memoria oltre ai puntatori intelligenti. Potrei voler gestire la memoria da solo, potrei allocare spazio per le cose in un pool di memoria, quindi è allocato in anticipo e non in fase di esecuzione (utile per i giochi). Potrei usare un'implementazione garbage collection di C ++ (C ++ 11 lo rende possibile anche se non ne esiste ancora). O forse non sto facendo nulla di abbastanza avanzato da preoccuparmi di disturbarli, posso sapere che non dimenticherò oggetti non inizializzati e così via. Forse sono solo sicuro della mia capacità di gestire la memoria senza la stampella del puntatore.

Anche l'integrazione con C è un altro problema.

Un altro problema è che i puntatori intelligenti fanno parte dell'STL. C ++ è progettato per essere utilizzabile senza STL.


" Un altro problema è che i puntatori intelligenti fanno parte dell'STL. " Non lo sono.
curioso

0

Dipende anche dal dominio in cui lavori. Scrivo motori di gioco per vivere, evitiamo boost come la peste, nei giochi il sovraccarico di boost non è accettabile. Nel nostro motore principale abbiamo finito per scrivere la nostra versione di stl (molto simile a ea stl).

Se dovessi scrivere un'applicazione per moduli, potrei prendere in considerazione l'uso di puntatori intelligenti; ma una volta che la gestione della memoria è una seconda natura, non avere un controllo granulare della memoria diventa abbastanza fastidioso.


3
Non esiste un "overhead di boost".
curioso

4
Non ho mai avuto shared_ptr rallentare il mio motore di gioco ad un livello degno di nota. Tuttavia, hanno accelerato il processo di produzione e debug. Inoltre, cosa intendi esattamente per "sovraccarico di boost?" È una coperta abbastanza grande da lanciare.
derpface

@curiousguy: è l'overhead di compilazione di tutte quelle intestazioni e macro + template voodoo ...
einpoklum
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.