2014-09-02 9 views
35

In C++ 11 è ancora necessario utilizzare std::localtime e std::gmtime come indiretto per stampare un std::chrono::time_point. Queste funzioni non sono sicure da utilizzare in un ambiente multithread come introdotto con C++ 11 perché restituiscono un puntatore a una struttura statica interna. Ciò è particolarmente fastidioso dal momento che C++ 11 ha introdotto la comoda funzione std::put_time che è quasi inutilizzabile per lo stesso motivo.Perché non esiste un'alternativa thread-safe in C++ 11 a std :: localtime e std :: gmtime?

Perché questo è così fondamentale rotto o trascuro qualcosa?

+2

Wow, queste funzioni sono quasi altrettanto ben progettati come 'strtok'. – CodesInChaos

+1

"La programmazione in C++ non dovrebbe essere facile" Suppongo che questa sia ancora la filosofia dominante - è necessario qualcosa che si scrive qualcosa a meno che non sia un minimo assoluto. – zzz777

risposta

11

localtime e gmtime hanno una memoria interna statica, il che significa che non sono thread-safe (dobbiamo restituire un puntatore a una struttura dati, quindi deve essere assegnato dinamicamente, un valore statico o un valore globale - dal allocare dinamicamente perderebbe memoria, che non è una soluzione ragionevole, il che significa che deve essere una variabile globale o statica [teoricamente, si potrebbe allocare e archiviare in TLS e renderlo thread-safe in questo modo]).

La maggior parte dei sistemi dispone di alternative thread-safe, ma non fanno parte della libreria standard. Ad esempio, Linux/Posix ha localtime_r e gmtime_r, che accetta un parametro aggiuntivo per il risultato. Si veda ad esempio http://pubs.opengroup.org/onlinepubs/7908799/xsh/gmtime.html

Analogamente, librerie Microsoft hanno gmtime_s, anch'esso rientrante e funziona in modo simile (passando nel parametro di uscita come ingresso). Vedi http://msdn.microsoft.com/en-us/library/3stkd9be.aspx

Per quanto riguarda il motivo per cui la libreria C++ 11 standard non utilizza queste funzioni? Che dovresti chiedere alla gente che ha scritto quella specifica - mi aspetto che sia portabilità e praticità, ma non ne sono del tutto sicuro.

+5

Si noti che 'localtime' e' gmtime' in Microsoft Visual C Runtime utilizzano TLS e sono thread-safe. (Vedi [qui] (http://stackoverflow.com/q/2278919/25507). Lo scopo di 'gmtime_s' è la sicurezza (aggiungendo la validazione dei parametri), non la rientranza (anche se il fatto che richiede un parametro di output lo consente funziona anche bene per questo). –

15

Secondo N2661, la carta che ha aggiunto <chrono>:

Questo documento non offre servizi calendariali ad eccezione di un minimo mappatura da e C di time_t.

Poiché questo documento non propone una libreria di data/ora, né specifica le epoche, non si rivolge anche ai secondi bisestili. Tuttavia, una libreria data/ora troverà questa una base eccellente su cui costruire .

Questo documento non propone una quantità fisica per le quantità fisiche .

Questo articolo propone una solida base che, in futuro, potrebbe fornire un punto di partenza compatibile per una biblioteca fisica generale delle unità . Mentre una tale libreria futura potrebbe assumere uno qualsiasi dei vari moduli, , la presente proposta si blocca ben poco di essere effettivamente una libreria fisica unità. Questa proposta è specifica per il tempo e continua ad essere motivata dalle esigenze relative al tempo della libreria di threading.

Il principale obiettivo di questa proposta è quello di soddisfare le esigenze del API standard libreria di threading in un modo che è facile da usare, sicuro da usare, efficiente, e sufficientemente flessibile per non essere obsolete 10 o addirittura tra 100 anni.Ogni caratteristica contenuta in questa proposta è qui per un motivo specifico con casi d'uso pratico come motivazione. Cose che rientrano nella categoria di "cool", o "sembra che possa essere utile" essere utile "o" molto utile ma non necessario da questa interfaccia "non sono stati inclusi . Tali articoli potrebbero apparire in altre proposte e potrebbe essere indirizzato a un TR.

Si noti che l'obiettivo principale di <chrono> è "soddisfare le esigenze dell'API di threading di libreria standard", che non richiede i servizi di calendario.

+0

Ok, questo potrebbe spiegare che il design ha scelto un po ', a parte l'introduzione di 'std :: put_time' senza un modo sicuro e indipendente dalla piattaforma di usarlo. – TNA

+1

@TNA Beh, era inteso come un [sostituto di 'strftime'] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2071.html), che ha anche bisogno a 'tm *'. –

+1

A mio parere, se si sostituisce una funzione C con una funzione C++, dovrebbe essere coerente in un modo, che non è necessario utilizzare funzioni C non sicure per utilizzarlo. Non esiste un modo sicuro, indipendente dalla piattaforma per ottenere un 'tm', quindi tutte le funzioni che usano un' tm' sono inutilizzabili con C++ 11 a parte alcuni casi speciali. Non esiste un modo standard per aggirarlo. – TNA

7

Non esiste un'alternativa sicura a std::localtime e std::gmtime perché non è stata proposta e gestita tramite l'intero processo di standardizzazione. E nemmeno nessun altro.

chrono s solo il codice di calendario è il codice che include le funzioni esistenti time_t. La standardizzazione o la scrittura di nuovi era al di fuori del dominio del progetto chrono. Fare una tale standardizzazione richiederebbe più tempo, più impegno e maggiori dipendenze. Semplicemente avvolgendo ogni funzione time_t era semplice, aveva poche dipendenze e veloce.

Hanno concentrato i loro sforzi in modo ristretto. E sono riusciti a quello su cui si sono concentrati.

Vi incoraggio a iniziare a lavorare su <calendar> oa partecipare a tale sforzo per creare una robusta API di calendario per std. Buona fortuna e buona fortuna!

2

Come altri hanno detto, non c'è davvero nessuna convenienza threadsafe e ora portatile formattazione approccio a qualsiasi standard C++ a disposizione, ma c'è qualche tecnica preprocessore arcaica ho trovato utilizzabili (grazie a Andrei Alexandrescu a CppCon 2015 slitta 17 & 18):

std::mutex gmtime_call_mutex; 

template< size_t For_Separating_Instantiations > 
std::tm const * utc_impl(std::chrono::system_clock::time_point const & tp) 
{ 
    thread_local static std::tm tm = {}; 
    std::time_t const time = std::chrono::system_clock::to_time_t(tp); 
    { 
     std::unique_lock<std::mutex> ul(gmtime_call_mutex); 
     tm = *std::gmtime(&time); 
    } 
    return &tm; 
} 


#ifdef __COUNTER__ 
#define utc(arg) utc_impl<__COUNTER__>((arg)) 
#else 
#define utc(arg) utc_impl<__LINE__>((arg)) 
#endif 

Qui dichiariamo funzione con size_t argomento di un template e il puntatore di tornare a membro statico std::tm. Ora ogni chiamata di questa funzione con argomenti modello diversi crea una nuova funzione con la nuova variabile statica std::tm. Se la macro __COUNTER__ è definita, dovrebbe essere sostituita dal valore intero incrementato ogni volta che viene utilizzata, altrimenti usiamo la macro __LINE__ e in questo caso meglio per essere sicuri di non chiamare la macro utc due volte in una riga.

globale gmtime_call_mutex proteggere non threadsafe std::gmtime chiamata in ogni esemplificazione, e almeno in Linux non dovrebbe essere un problema di prestazioni, come l'acquisizione di blocco viene dapprima eseguita come correre spinlock, e nel nostro caso non dovrebbe mai finire con filo reale serratura.

thread_local assicura che thread diversi che eseguono lo stesso codice con le chiamate utc funzionino ancora con diverse variabili std::tm.

Esempio di utilizzo:

void work_with_range(
     std::chrono::system_clock::time_point from = {} 
     , std::chrono::system_clock::time_point to = {} 
     ) 
{ 
    std::cout << "Will work with range from " 
     << (from == decltype(from)() 
       ? std::put_time(nullptr, "beginning") 
       : std::put_time(utc(from), "%Y-%m-%d %H:%M:%S") 
      ) 
     << " to " 
     << (to == decltype(to)() 
       ? std::put_time(nullptr, "end") 
       : std::put_time(utc(to), "%Y-%m-%d %H:%M:%S") 
      ) 
     << "." 
     << std::endl; 
    // ... 
} 
+0

Si noti che ciò non proteggerebbe dall'uso esterno concorrente di gmtime o localtime nello stesso processo. – evoskuil

1

Se siete disposti ad utilizzare un free, open-source 3rd party library, ecco un modo per stampare std::chrono::system_clock::time_point in UTC:

#include "date.h" 
#include <iostream> 

int 
main() 
{ 
    using namespace date; 
    using namespace std::chrono; 
    std::cout << system_clock::now() << " UTC\n"; 
} 

Questa è un'alternativa thread-safe per std::gmtime usando la sintassi C++ moderna.

Per un moderno thread-safe std::localtime sostituzione, avete bisogno di questo strettamente legato higher level timezone library e la sintassi simile a questa:

#include "tz.h" 
#include <iostream> 

int 
main() 
{ 
    using namespace date; 
    using namespace std::chrono; 
    std::cout << make_zoned(current_zone(), system_clock::now()) << "\n"; 
} 

Entrambi questi uscita volontà con qualsiasi precisione i vostri system_clock supporti, ad esempio:

2016-07-05 10:03:01.608080 EDT 

(microsecondi su MacOS)

Queste librerie vanno ben oltre una gmtime e localtime sostituzione. Ad esempio, vuoi vedere la data corrente nel calendario giuliano?

#include "julian.h" 
#include <iostream> 

int 
main() 
{ 
    using namespace std::chrono; 
    std::cout << julian::year_month_day(date::floor<date::days>(system_clock::now())) << "\n"; 
} 

2016-06-22 

Come circa l'ora GPS corrente?

#include "tz.h" 
#include <iostream> 

int 
main() 
{ 
    using namespace date; 
    std::cout << std::chrono::system_clock::now() << " UTC\n"; 
    std::cout << gps_clock::now() << " GPS\n"; 
} 

2016-07-05 14:13:02.138091 UTC 
2016-07-05 14:13:19.138524 GPS 

https://github.com/HowardHinnant/date

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r0.html

+0

Siamo spiacenti, ma il primo esempio è sbagliato: std :: cout << system_clock :: now() << "UTC \ n"; prima di tutto, non verrà compilato, in secondo luogo non è in UTC (a meno che il computer sia impostato sul fuso orario UTC) – Tadzys

+0

@Tadzys: tutti gli esempi sopra riportati sono programmi minimi testati prima di pubblicare utilizzando le librerie di riferimento. Secondo: l'epoca di 'system_clock' non è specificata. Ma lo standard di fatto tra tutte e tre le implementazioni è che tiene traccia di Unix Time (https://en.wikipedia.org/wiki/Unix_time). Sto tentando di ottenere questa pratica corrente standardizzata: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r1.html –

+0

scusa, mia cattiva, non ho capito che tz. h ha sovraccaricato l'operatore << per la struttura time_point, e sì, ho sbagliato anche sul mio secondo punto (questo potrebbe spiegare il mio errore che sto avendo :) – Tadzys