Duplicata possible:
Valeurs nulles en C++
Quelle est la meilleure façon de représenter un membre Nullable en C++?
En C #, nous pouvons utiliser Nullable<T>
type. Un tel type de données est absolument nécessaire car tout ne peut pas avoir significatif valeur. C'est un type de données si important que @ Jon Skeet a passé un chapitre entier, couvrant plus de 27 pages, décrivant seulement Nullable<T>
dans son livre exceptionnel C # en profondeur .
Un exemple simple peut être une classe Person
1, défini comme:
struct Person
{
std::string Name;
DateTime Birth;
DateTime Death;
//...
};
Comme une personne a toujours une date de naissance, le membre Birth
de la classe ci-dessus aura toujours une valeur significative. Mais qu'en est-il de Death
? Quelle devrait être la valeur si la personne est vivante? En C #, ce membre peut être déclaré comme Nullable<DataTime>
2 qui peut être attribué avec null
si la personne est vivante.
En C++, quelle est la meilleure façon de résoudre ce problème? Pour l'instant, je n'ai qu'une seule solution en tête: déclarer le membre comme pointer:
DataTime *Death;
Maintenant, sa valeur peut être nullptr
lorsque la personne est vivante. Mais cela force l'utilisation de new
pour une personne décédée, car il aura une valeur valide. Cela implique à son tour que l'on ne peut pas se fier au code default copie-sémantique généré par le compilateur. Le programmeur doit écrire le constructeur de copie, l'affectation de copie, le destructeur suivant règle de trois (C++ 03), Ou en C++ 11, règle de cinq .
Alors avons-nous une solution meilleure et élégante à ce problème que de simplement le faire pointer?
1. D'autres exemples incluent les tables de bases de données relationnelles, car dans de nombreux SGBD, les colonnes peuvent être nulles.
2. Il existe également un raccourci pour cela. On peut écrire DataTime?
qui est exactement identique à Nullable<DateTime>
.
Vous pouvez examiner Boost.Optional :
struct Person
{
std::string Name;
DateTime Birth;
boost::optional<DateTime> Death;
//...
};
Death
est "non initialisé" au début.=
, Comme Death = myDateTime
.Death.is_initialized()
, vous pouvez utiliser Death.get()
.Death.reset()
.Pour des cas simples comme celui-ci, cependant, il est généralement considéré comme plus cohérent de choisir simplement votre propre valeur sentinelle flagrante comme, par exemple, un DateTime
de "0000-00-00 00:00:00".
Dépend de DateTime
- comme @Tomalak le dit dans sa réponse, boost::optional<>
est une solution générique. Cependant si par exemple votre DateTime
est un boost::posix_time::ptime
, alors il existe déjà un support pour valeurs spéciales (par exemple not_a_date_time
ou pos_infin
) - vous pouvez les utiliser.
Chaque projet sur lequel j'ai travaillé a eu une sorte de classe de modèle Fallible
, Maybe
ou Nullable
. (Le nom réel a tendance à refléter ce pour quoi l'application en avait d'abord besoin: Fallible
comme valeur de retour, Nullable
pour modéliser les bases de données, etc.). Plus récemment, Boost a introduit boost::optional
; malheureusement, ils utilisent des conversions implicites au lieu d'une fonction isValid
(nommée), ce qui entraîne un code nettement moins lisible (au point que je l'éviterais, sauf peut-être pour implémenter mon propre Maybe
).
Vous pouvez faire ce que des légions de programmeurs ont fait avant vous! Utilisez une valeur spécifique comme "non présent" ... Par exemple, j'ai entendu dire que "1 janvier 2000" était assez courant :-) :-) Pour des raisons d'interopérabilité, vous pouvez utiliser "19 janvier 2038 03:14:07 UTC" :-) :-) (c'est une blague, si ce n'est pas clair. Je fais référence au problème de l'an 2000 et au problème de l'Y2038. Je montre le problème de l'utilisation de dates "spéciales" comme états ... Des choses comme 11 -11-11 et similaires)
La valeur maximum/minimum de votre DataTime
est probablement plus correcte :-) Et c'est toujours faux, car vous mélangez un "état" avec une "valeur". Mieux vaut reconstruire le type Nullable
en C++ (au final c'est assez simple: une classe basée sur un modèle avec un bool pour null/non null et le champ T)
comme la mort est particulièrement improbable à n'importe quel moment avant naissance, vous pouvez tout aussi bien le régler à la naissance - 1 au départ et le faire changer sur l'événement réel. en termes plus courants, vous appelleriez probablement naissance - 1 une valeur sentinelle ou une valeur d'espace réservé. vous pouvez également choisir une valeur constante suffisamment basse pour ne pas être confondue avec une valeur réelle, mais cela suppose que vous ayez une certaine connaissance de vos données.