web-dev-qa-db-fra.com

Quel trait/concept peut garantir que la création d'un objet est bien définie?

Disons que j'ai défini une fonction zero_initialize():

template<class T>
T zero_initialize()
{
    T result;
    std::memset(&result, 0, sizeof(result));
    return result;
}

// usage: auto data = zero_initialize<Data>();

L'appel de zero_initialize() pour certains types entraînerait un comportement indéfini1, 2. J'applique actuellement T pour vérifier std::is_pod . Ce trait étant obsolète en C++ 20 et l’apparition de concepts, je suis curieux de savoir comment zero_initialize() devrait évoluer.

  1. Quel trait/concept (minimal) peut garantir que la création d'un objet est bien définie?
  2. Devrais-je utiliser std::uninitialized_fill au lieu de std::memset? Et pourquoi?
  3. Cette fonction est-elle rendue obsolète par l'une des syntaxes d'initialisation C++ pour un sous-ensemble de types? Ou sera-ce avec les prochaines versions de C++ à venir?

1) Effacer tous les membres d'une classe .
2) Quelle serait la raison pour les “comportements indéfinis” sur l'utilisation de memset sur la classe de bibliothèque (std :: string)? [fermé]

20
YSC

Techniquement, aucune propriété d'objet en C++ ne spécifie que le code utilisateur peut légalement memset un objet C++. Et cela inclut POD, donc si vous voulez être technique, votre code n’a jamais été correct. Même TriviallyCopyable est une propriété permettant de copier des octets entre des objets existants (parfois via un tampon d'octets intermédiaire); cela ne dit rien d'inventer des données et de les insérer dans les bits de l'objet.

Ceci étant dit, vous pouvez être raisonnablement sûr que cela fonctionnera si vous testez is_trivially_copyableetis_trivially_default_constructible. Ce dernier point est important, car certains types TriviallyCopyable veulent toujours pouvoir contrôler leur contenu. Par exemple, un tel type pourrait avoir une variable privée int toujours égale à 5, initialisée dans son constructeur par défaut. Tant qu'aucun code ayant accès à la variable ne la modifie, il restera toujours à 5. Le modèle d'objet C++ le garantit.

Donc, vous ne pouvez pas memset un tel objet et malgré tout obtenir un comportement bien défini à partir du modèle d'objet.

23
Nicol Bolas

Quel trait/concept (minimal) peut garantir que la création d'un objet est bien définie?

Selon la référence std::memset sur cppreference , le comportement de memset sur un type non TriviallyCopyable n'est pas défini. Donc, si vous pouvez utiliser memset a TriviallyCopyable, vous pouvez ajouter un static_assert à votre classe pour vérifier que

template<class T>
T zero_initialize()
{
    static_assert(std::is_trivial_v<T>, "Error: T must be TriviallyCopyable");
    T result;
    std::memset(&result, 0, sizeof(result));
    return result;
}

Ici, nous utilisons std::is_trivial_v pour nous assurer que non seulement la classe est trivialement copiable, mais qu’elle a également un constructeur trivial par défaut, nous savons donc qu’elle peut être initialisée à zéro.

Devrais-je utiliser std::uninitialized_fill au lieu de std::memset? Et pourquoi?

Vous n’avez pas besoin d’être ici puisque vous n’initialisez qu’un seul objet.

Cette fonction est-elle rendue obsolète par l'une des syntaxes d'initialisation C++ pour un sous-ensemble de types? Ou sera-ce avec les prochaines versions de C++ à venir?

L’initialisation par valeur ou par hachage rend cette fonction "obsolète". T() et T{} vous donneront une valeur initialisée T et si T ne possède pas de constructeur par défaut, il sera initialisé à zéro. Cela signifie que vous pouvez réécrire la fonction en tant que

template<class T>
T zero_initialize()
{
    static_assert(std::is_trivial_v<T>, "Error: T must be TriviallyCopyable");
    return {};
}
8
NathanOliver

Le trait définissable le plus général qui garantit que votre zero_initialize initialisera les objets à zéro est

template <typename T>
struct can_zero_initialize :
    std::bool_constant<std::is_integral_v<
        std::remove_cv_t<std::remove_all_extents_t<T>>>> {};

Pas trop utile. Mais la seule garantie concernant la représentation bit/bit ou par octet des types fondamentaux dans la norme est [basic.fundamental]/7 "Les représentations des types intégraux doivent définir les valeurs à l'aide d'un système de numération binaire pur." Il n'y a aucune garantie qu'une valeur à virgule flottante avec tous les octets zéro soit une valeur zéro. Il n'y a aucune garantie que tout pointeur ou valeur de pointeur sur membre comportant tous les octets zéro soit une valeur de pointeur nulle. (Bien que les deux soient généralement vrais dans la pratique.)

Si tous les membres non statiques d'un type de classe trivialement copiable sont (des tableaux de) types intégraux (qualifiés de cv), je pense que ce serait également correct, mais il n'y a aucun moyen de tester cela, à moins que C++ ne fasse l'objet d'une réflexion.

0
aschepler