Rappresentare i tempi in C++

In questo periodo sto cominciando a smanettare con C# e, tra i vari tipi che fanno parte del framework .NET ce ne sono due adatti per rappresentare i tempi: DateTime e TimeSpan, entrambi disponibili nel namespace System. Essenzialmente, il primo rappresenta una data e ora, mentre il secondo una durata e può essere istanziato direttamente o a seguito del risultato della sottrazione tra due DateTime.

In C++, purtroppo, non c’è niente di simile, almeno nella STL. Sì, certo, esiste il file di intestazione <chrono>, ma non è flessibile quanto vorrei. Per esempio, non consente di rappresentare una durata in ore, minuti e secondi, il che è una grande scocciatura. Inoltre, la sintassi di chrono è a dir poco complicata e anche un po’ bruttina, quindi per SRT validation mi serviva qualcosa di un po’ più facile da usare.

A onor del vero, inizialmente avevo pensato di memorizzare le informazioni sul tempo di inizio e di fine di un sottotitolo semplicemente come stringhe, un po’ per lasciare il codice un po’ più leggibile, un po’ perché sono un pigrone del cazzo. Però a volte bisogna mettere da parte la pigrizia e darsi da fare.

L’idea di base

Eliminata l’ipotesi di usare le stringhe, dovevo scrivere una classe per rappresentare questi tempi.

In un file SRT, il tempo di inizio e di fine di un determinato sottotitolo è rappresentato da una riga del tipo

00:00.000 --> 00:01.000

in cui la parte a sinistra è il tempo di inizio e quella a destra è il tempo di fine.

Per il momento non mi sto curando dei dettagli dell’implementazione di una conversione da questo tipo di riga: mi basta sapere che ciascun tempo è costituito da tre elementi: minuti, secondi e millisecondi.

Fortunatamente chrono include tre tipi che rappresentano queste unità di misura, chiamati rispettivamente

std::chrono::minutes      //minuti
std::chrono::seconds      //secondi
std::chrono::milliseconds //millisecondi

Dunque posso scrivere una classe contenente tre attributi privati di questi tre tipi, uno o due costruttori pubblici e due overloading degli operatori interni alla classe. Il risultato è questo file di intestazione:

#pragma once
#include <chrono>
#include <iostream>
/**
*@file Time.h
*@class Time
*@brief A class used to represent time codes in a subtitle.
*
* Time.h allows to represent time codes in a subtitle as minutes, seconds and milliseconds.
* It contains three std::chrono::time_point private members and two operator overloadings.
*/
class Time
{
public:
    Time();
    Time(int, int, int);
    bool operator>(Time&);
    std::ostream& operator<< (std::ostream&);
private:
    ///@brief Number of minutes.
    std::chrono::minutes minutes;
    ///@brief Number of seconds.
    std::chrono::seconds seconds;
    ///@brief Number of milliseconds.
    std::chrono::milliseconds milliseconds;
};

Notare come non vi sono metodi getter e setter. Il motivo è semplice: in SRT Validation non vengono eseguite modifiche al file, ma si controlla solamente se questo file SRT è valido oppure no, quindi non ho bisogno di dover modificare questi valori in un secondo momento. D’altro canto, aggiungere tali metodi non è poi molto difficile.

Questo però è solo il file di intestazione, privo dei dettagli dell’implementazione, definiti nel file Time.cpp.

Ecco il suo contenuto:

#include "Time.h"

/**
* @brief Time default constructor
*
* Inizializes the time as 00:00.000
*/
Time::Time() {
    this->minutes = (std::chrono::minutes)0;
    this->seconds = (std::chrono::seconds)0;
    this->milliseconds = (std::chrono::milliseconds)0;
}

/**
*@brief Time normal constructor
*
* Inizializes the time with three integer values
* @param minutes Number of minutes.
* @param seconds Number of seconds.
* @param milliseconds Number of milliseconds.
*/
Time::Time(int minutes, int seconds, int milliseconds) {
    this->minutes = (std::chrono::minutes) minutes;
    this->seconds = (std::chrono::seconds) seconds;
    this->milliseconds = (std::chrono::milliseconds) milliseconds;
}

/**
*@brief Overloading of operator > for comparisons
*
*The operator > can be used to compare two of these times. This is useful when checking for overlapping subtitles.
*@param tm The right-hand operand.
*/
bool Time::operator>(Time& tm) {
    if (this->minutes > tm.minutes) {
        return true;
    } 
    else if (this->seconds > tm.seconds) {
        return true;
    }
    else if (this->milliseconds > tm.milliseconds) {
        return true;
    }
    return false;
}

/**
*@brief Overloading of operator << for ostream
*
*This member function allows us to use our Time class with ostreams. It prints the number of minutes, seconds and milliseconds separated by their respective a ":" and a ".", respectively.
@param os A reference to an ostream.
*/
std::ostream& Time::operator<<(std::ostream& os) {
    return os << minutes.count() << ":" << seconds.count() << "." << milliseconds.count();
}

In questo codice molto semplice abbiamo alcune cose da attenzionare.

Costruttori

Abbiamo definito due costruttori, uno che non accetta argomenti e uno che accetta tre interi. Il primo inizializza i tre attributi privati a 0, mentre il secondo assegna loro i tre parametri. Fin qui, niente di trascendentale, ma bisogna notare che i tipi definiti in chrono non possono essere convertiti implicitamente da o a intero, quindi abbiamo avuto bisogno di effettuare un casting esplicito, in questo caso usando il casting C-style, anche se forse sarebbe stato più opportuno usare uno static_cast.

Overloading >

Dal momento che SRT Validation dovrà effettuare controlli per verificare che un tempo di fine di un sottotitolo e il tempo di inizio di quello successivo non si sovrappongano, mi serve un metodo che faccia questo paragone. Avrei potuto definire un metodo pubblico isOverlap che prendesse come argomento un riferimento a un oggetto di tipo Time, ma avevo voglia di far morire d’invidia i javisti 😀

Il codice di questo metodo è molto semplice: paragona i vari attributi privati dei due operandi. Se nessuno dei confronti restituisce true, viene restituito false. Notare che ho omesso l’else perché in questo caso non avrebbe fatto alcuna differenza.

Overloading << per ostream

Questo metodo è abbastanza semplice da capire, ma vale un ragionamento simile a quello che abbiamo fatto per i costruttori, solo che in questo caso è anche peggio: non possiamo usare l’operatore << con un std::chrono::seconds (o minutes o milliseconds, se è per questo)!

L’unica alternativa è usare il metodo count(), che restituisce un tipo aritmetico.

Leave a Comment

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *