web-dev-qa-db-fra.com

La meilleure façon de représenter un membre Nullable en C ++?

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 Person1, 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>.

33
Nawaz

Vous pouvez examiner Boost.Optional :

struct Person
{
  std::string               Name;
  DateTime                  Birth;
  boost::optional<DateTime> Death;
  //...
};
  • Votre Death est "non initialisé" au début.
  • Vous pouvez ensuite lui attribuer une valeur avec =, Comme Death = myDateTime.
  • Lorsque Death.is_initialized(), vous pouvez utiliser Death.get().
  • Désinitialisez-le à nouveau avec 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.

6
Nim

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 ).

5
James Kanze

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)

1
xanatos

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.

1
mtijn