C ++ Wie konvertiere ich einen std :: chrono :: time_point in long und back?

78

Ich muss zu std::chrono::time_pointund von einem longTyp konvertieren (Ganzzahl 64 Bit). Ich fange an mit std::chrono...

Hier ist mein Code:

int main ()
{
     std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();

    auto epoch = now.time_since_epoch();
    auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
    long duration = value.count();


    std::chrono::duration<long> dur(duration);

    std::chrono::time_point<std::chrono::system_clock> dt(dur);

    if (dt != now)
        std::cout << "Failure." << std::endl;
    else
        std::cout << "Success." << std::endl;
}

Dieser Code wird kompiliert, zeigt jedoch keinen Erfolg.

Warum ist dtanders als nowam Ende?

Was fehlt in diesem Code?

Mendes
quelle
1
Ich bin mit der Chronobibliothek nicht allzu vertraut, aber ich glaube, Sie müssen sie verwenden, std::chrono::duration<long,std::milli> durund selbst dann können Rundungsfehler auftreten ( std::chrono::system_clockhat wahrscheinlich eine höhere Auflösung als Millisekunden).
MikeMB
@MikeMB Alle Uhren auf einigermaßen neuer Hardware sollten ohnehin eine Genauigkeit von etwa Mikrosekunden / Submikrosekunden haben. Siehe einen Artikel mit dem Titel Die drei Uhren . Tatsächlich haben Fenster normalerweise eine höhere Präzision mit system_clock(ungefähr 100-mal höher), aber selbst dies ist normalerweise weniger als eine Mikrosekunde.
Werlious

Antworten:

158
std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();

Dies ist ein großartiger Ort für auto:

auto now = std::chrono::system_clock::now();

Da Sie millisecondpräzise verkehren möchten , ist es gut, in den time_pointfolgenden Schritten zu verdecken :

auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);

now_msist ein time_point, basierend auf system_clock, aber mit der Präzision von millisecondsanstelle der Präzision, die Sie system_clockhaben.

auto epoch = now_ms.time_since_epoch();

epochhat jetzt typ std::chrono::milliseconds. Und diese nächste Anweisung wird im Wesentlichen zu einem No-Op (erstellt einfach eine Kopie und führt keine Konvertierung durch):

auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);

Hier:

long duration = value.count();

Enthält sowohl in Ihrem als auch in meinem Code durationdie Nummer millisecondsseit der Epoche von system_clock.

Dies:

std::chrono::duration<long> dur(duration);

Erstellt eine durationDarstellung mit a longund einer Genauigkeit von seconds. Dies ist effektiv reinterpret_castdas millisecondsFesthalten valuean seconds. Es ist ein logischer Fehler. Der richtige Code würde folgendermaßen aussehen:

std::chrono::milliseconds dur(duration);

Diese Linie:

std::chrono::time_point<std::chrono::system_clock> dt(dur);

Erstellt eine time_pointbasierend auf system_clock, mit der Fähigkeit, eine Genauigkeit auf der system_clocknativen Genauigkeit der (normalerweise feiner als Millisekunden) zu halten. Der Laufzeitwert gibt jedoch korrekt wieder, dass eine ganzzahlige Anzahl von Millisekunden gehalten wird (unter der Annahme meiner Korrektur des Typs von dur).

Trotz der Korrektur schlägt dieser Test (fast immer) fehl:

if (dt != now)

Weil dteine ganzzahlige Anzahl von milliseconds, aber noweine ganzzahlige Anzahl von Zecken feiner als a millisecond(zB microsecondsoder nanoseconds) enthält. Somit würde der Test nur bei der seltenen Chance, dass system_clock::now()eine ganzzahlige Anzahl von zurückgegeben millisecondswird, bestehen.

Aber Sie können stattdessen:

if (dt != now_ms)

Und Sie erhalten jetzt zuverlässig Ihr erwartetes Ergebnis.

Alles zusammen:

int main ()
{
    auto now = std::chrono::system_clock::now();
    auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);

    auto value = now_ms.time_since_epoch();
    long duration = value.count();

    std::chrono::milliseconds dur(duration);

    std::chrono::time_point<std::chrono::system_clock> dt(dur);

    if (dt != now_ms)
        std::cout << "Failure." << std::endl;
    else
        std::cout << "Success." << std::endl;
}

Persönlich finde ich das alles sehr std::chronoausführlich und würde es so codieren als:

int main ()
{
    using namespace std::chrono;
    auto now = system_clock::now();
    auto now_ms = time_point_cast<milliseconds>(now);

    auto value = now_ms.time_since_epoch();
    long duration = value.count();

    milliseconds dur(duration);

    time_point<system_clock> dt(dur);

    if (dt != now_ms)
        std::cout << "Failure." << std::endl;
    else
        std::cout << "Success." << std::endl;
}

Welches wird zuverlässig ausgeben:

Success.

Schließlich empfehle ich, temporäre Elemente zu entfernen, um die Konvertierung von Code zwischen time_pointund Integraltyp auf ein Minimum zu reduzieren . Diese Konvertierungen sind gefährlich. Je weniger Code Sie schreiben, um den nackten Integraltyp zu manipulieren, desto besser:

int main ()
{
    using namespace std::chrono;
    // Get current time with precision of milliseconds
    auto now = time_point_cast<milliseconds>(system_clock::now());
    // sys_milliseconds is type time_point<system_clock, milliseconds>
    using sys_milliseconds = decltype(now);
    // Convert time_point to signed integral type
    auto integral_duration = now.time_since_epoch().count();
    // Convert signed integral type to time_point
    sys_milliseconds dt{milliseconds{integral_duration}};
    // test
    if (dt != now)
        std::cout << "Failure." << std::endl;
    else
        std::cout << "Success." << std::endl;
}

Die Hauptgefahr oben ist nicht das Dolmetschen integral_durationwie millisecondsauf dem Weg zurück zu a time_point. Eine Möglichkeit, dieses Risiko zu mindern, besteht darin, Folgendes zu schreiben:

    sys_milliseconds dt{sys_milliseconds::duration{integral_duration}};

Dies reduziert das Risiko, indem Sie nur sicherstellen, dass Sie es sys_millisecondsauf dem Weg nach draußen und an den beiden Stellen auf dem Weg zurück verwenden.

Und noch ein Beispiel: Angenommen, Sie zu und von einem integrierten konvertieren wollen , das unabhängig von Dauer repräsentiert system_clockStützen (Mikrosekunden, 10 th von Mikrosekunden oder Nanosekunden). Dann müssen Sie sich nicht mehr um die Angabe von Millisekunden wie oben kümmern. Der Code vereinfacht sich zu:

int main ()
{
    using namespace std::chrono;
    // Get current time with native precision
    auto now = system_clock::now();
    // Convert time_point to signed integral type
    auto integral_duration = now.time_since_epoch().count();
    // Convert signed integral type to time_point
    system_clock::time_point dt{system_clock::duration{integral_duration}};
    // test
    if (dt != now)
        std::cout << "Failure." << std::endl;
    else
        std::cout << "Success." << std::endl;
}

Dies funktioniert, aber wenn Sie die Hälfte der Konvertierung (von in Integral) auf einer Plattform und die andere Hälfte (von Integral) auf einer anderen Plattform ausführen, besteht das Risiko, dass system_clock::durationdie beiden Konvertierungen unterschiedliche Präzisionen haben.

Howard Hinnant
quelle
1
@BrentNash: Vorsicht, ich könnte dich darauf ansprechen. ;-) Ich werde auf der Cppcon 2015 ( cppcon.org ) darüber sprechen: howardhinnant.github.io/date_v2.html . Das Thema ist sehr eng mit dem time_point_cast<milliseconds>(now)in meiner obigen Antwort verwandten verwandt . Nur die Dauer ist Courser : time_point_cast<days>(now).
Howard Hinnant
Tippfehler im letzten Kommentar: gröber, nicht Courser. Tolle Antwort.
Dirk Eddelbuettel
2
Das Problem mit Englisch ist, dass es mit einem C ++ - Compiler nicht analysiert werden kann und der Compiler eine bessere Rechtschreibprüfung bietet. :-)
Howard Hinnant
3
Dies ist sehr hilfreich, obwohl ich denke, dass es ein bisschen besser gewesen wäre, wenn Sie nicht überall 'auto' verwendet hätten. Nur für faule Leute wie mich, die sehen möchten, welche Typen manipuliert werden, ohne woanders suchen zu müssen. Vielen Dank @HowardHinnant
Markus L.
6

Ich würde auch bemerken, dass es zwei Möglichkeiten gibt, die Anzahl der ms zum Zeitpunkt zu ermitteln. Ich bin mir nicht sicher, welches besser ist. Ich habe sie verglichen und beide haben die gleiche Leistung. Ich denke, es ist eine Frage der Präferenz. Vielleicht könnte Howard mitmachen:

auto now = system_clock::now();

//Cast the time point to ms, then get its duration, then get the duration's count.
auto ms = time_point_cast<milliseconds>(now).time_since_epoch().count();

//Get the time point's duration, then cast to ms, then get its count.
auto ms = duration_cast<milliseconds>(tpBid.time_since_epoch()).count();

Der erste liest sich in meinem Kopf deutlicher von links nach rechts.

Fühlen Sie sich
quelle
1

als einzelne Zeile:

long value_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch()).count();
Harald P.
quelle
0

time_pointObjekte unterstützen nur Arithmetik mit anderen time_pointoder durationObjekten.

Sie müssen Ihre longin eine durationder angegebenen Einheiten konvertieren , dann sollte Ihr Code korrekt funktionieren.

Herr Lama
quelle
Können Sie ein Beispiel posten?
Mendes
Beide bereitgestellten Links haben unten einen Abschnitt "Beispiel".
Mr. Llama
Ich habe es kompiliert, aber es gibt einen logischen Fehler ... Ich habe den Originalcode bearbeitet ...
Mendes
1
Es wäre von Vorteil, wenn die Antwort Beispiele enthält. Links sterben oder ändern sich. Davon abgesehen bin ich mir nicht sicher, warum dies abgelehnt wurde.
TankorSmash