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.