Nous avons beaucoup de code existant utilisant des pointeurs bruts, qui est jonché de vérifications nulles à presque chaque utilisation.
En essayant d'écrire du code plus récent plus proprement, nous essayons d'utiliser des méthodes de constructeur d'usine et des uniques_ptrs. p>
Ma question est, dans le code ci-dessous, une fois que nous avons notre objet créé en usine - sensorX - pouvons-nous l'utiliser dans le reste du code sans autres vérifications nulles, puisqu'il s'agit d'un const unique_ptr?
DeviceFactory.h
const auto sensorX = DeviceFactory::create<Sensor>(123, "sensorX");
Usage
class DeviceFactory { public: template<typename T> static unique_ptr<T> create(int id, std::string status) { auto device = unique_ptr<T>{ new T{ id, status } }; if (!device) throw DeviceCreationException("device couldn't be created"); return device; } };
5 Réponses :
Un unique_ptr
peut toujours être nullptr
, cela signifie simplement que cet objet gère la ressource sous-jacente, pas qu'il y a une ressource acquise.
Vous devez donc toujours vérifier si l'objet existe ou non, comme vous l'avez fait auparavant.
Remarque: qu'est-ce qui interdit à quelqu'un d'appeler votre usine et de ne pas stocker le résultat dans un unique_ptr
? Tant que cela est autorisé, vous devrez vérifier.
"unique_ptr peut toujours être nullptr" - Peut-il dans mon exemple ci-dessus cependant, où j'ai déjà fait une vérification "if (! device)", et l'objet créé est const?
Vous passez ce unique_ptr en tant que const & partout?
oui, nous nous attendons à ajouter const dans la mesure du possible, là où il a été déclaré const en premier lieu. Notre ensemble d'outils comprend Klocwork et Resharper C ++ qui identifie les zones où nous pouvons ajouter const.
Vous pouvez le faire même s'il ne s'agissait pas d'un const unique_ptr, à condition de ne jamais le réinitialiser à null.
Sur votre fonction d'usine,
auto device = unique_ptr<T>{ new T{ id, status } }; if (!device) throw DeviceCreationException("device couldn't be created");
la vérification dans la ligne deux devrait être supprimé car le simple new
ne renvoie jamais null (enfin, à moins que vous ne fassiez quelque chose de vraiment horrible avec les valeurs par défaut de l'implémentation, je suppose).
Vous dites "vous n'avez jamais besoin de vérifier si c'est nul, si vous ne le définissez jamais sur nul"?
Tout d'abord le test:
template<typename T> static T create(int id, std::string status) { auto device = T{ id, status }; return device; }
n'a pas beaucoup de sens, car au moment où cette clause if est atteinte, ce ne serait pas possible pour device code> être
nullptr
(du moins pas pour le code donné)
Du point de vue de l'utilisateur, on sait que la fonction renvoie un code unique_ptr > en raison de la signature, mais vous devez lire la documentation, si l'appel
DeviceFactory::create
garantit qu'il ne contient pas nullptr
. Et même si la documentation le garantissait, cela pourrait être une bonne idée de le tester vous-même, car vous ne le savez pas, ce sera toujours le cas à l'avenir.
La question est donc de savoir si vous enregistrez-le toujours sous const unique_ptr
alors pourquoi avez-vous besoin d'un pointeur au lieu d'écrire:
if (!device) throw DeviceCreationException("device couldn't be created");
Mais revenons à votre question. Si const auto sensorX = ...
est initialisé avec un unique_ptr
qui ne contient pas nullptr
, alors il est garanti qu'il ne sera pas nullptr pour sa durée de vie.
"Donc, la question est de savoir si vous l'enregistrez toujours sous const unique_ptr, alors pourquoi avez-vous besoin d'un pointeur au lieu d'écrire:" Il semble que je n'ai pas vraiment posé ma question assez clairement, mais votre réponse @ t.niese est parfait pour la plupart de nos scénarios. Connaître le périphérique est non nul et constant lors de la création est ce que nous recherchons.
Bonne question: dans ce cas, faites par valeur. Cependant, ce n'est pas toujours possible lorsque vous avez des méthodes virtuelles pures ou lorsque vous ne pouvez pas vous déplacer (avant C ++ 17?)
Une fois que nous aurons notre objet créé en usine - sensorX - pouvons-nous l'utiliser dans le reste du code sans autres vérifications nulles?
Comme d'autres réponses l'ont indiqué, la réponse est "non". Mais ce que vous pouvez faire, c'est utiliser le modèle owner<>
( Implémentation MS ), décrit dans les directives:
owner
; owner
plutôt que T *
comme paramètres. Cela garantira, statiquement , que vous vous êtes assuré d'être propriétaire avant de passer l'appel - vous n'avez donc pas besoin de vérifier
On dirait que vous avez écrit votre propre version de std :: make_unique
avec une API moins flexible. Je recommanderais d'aligner l'API car cela facilitera les mises à niveau.
Cela dit, votre question concerne le contrôle nul. Comme indiqué à plusieurs reprises dans les commentaires, vous n'avez pas besoin de vérifier après avoir obtenu un pointeur, car std :: bad_alloc devrait être lancé. Cependant, supposons que vous ayez une autre vérification qui lève une telle exception personnalisée, que la question principale est: quelle est votre API, y compris les conditions préalables et postérieures.
Dans la base de code sur laquelle nous travaillons, std :: unique_ptr n'est pas autorisé à être nullptr à moins d'être documenté. Dans ce cas: pas besoin de contrôle nul. Vous pouvez le documenter comme étant nullptr, ou renvoyer un optionnel à la place, ce qui pourrait indiquer l'état non valide.
Mon habitude serait de m'affirmer sur le pointeur avant utilisation ou après création. Cependant, vous pouvez automatiser ce travail en utilisant les désinfectants de Clang / Gcc. Le GSL a également des vérifications disponibles pour cette utilisation de nullptr.
Ma suggestion: affirmer après avoir créé la variable (ou utiliser des désinfectants), qui devrait trouver le bogue lors des tests si les post-conditions devaient changer. Bien qu'avec vos collèges, vous devriez convenir que unique_ptr ne devrait pas être autorisé à contenir null à moins d'être documenté ce que nullptr représente.
Compris qu'il s'agissait d'une version plus pauvre de make_unique, dont nous n'étions pas au courant. "assert sur le pointeur avant l'utilisation ou après la création" - voulez-vous dire juste une fois après la création, avec l'hypothèse que si const, il ne peut pas être défini sur nullptr? Ou voulez-vous dire avant CHAQUE utilisation, comme les clauses de garde de Fowler?
Utilisez
std :: make_unique
Nous ne sommes actuellement autorisés à utiliser que C ++ 11.
Ensuite, écrivez votre propre
your_cool_namespace :: make_unique
. Votre fonction est exactement la même quemake_unique
(seul leif (! Device)
est redondant, il ne peut pas être nul, carnew
jette ). Pas besoin de réinventer la roue. Comment implémenter make-unique en C ++ 11 .Votre
throw
est redondant carnew
le lancera s'il échoue à allouer.(a) OP ne peut pas utiliser
make_unique
donc ils écrivent le leur. (b) Contributeur réprimande OP pour ne pas avoir utilisémake_unique
. (c) OP souligne qu'ils ne peuvent pas utilisermake_unique
. (d) Le contributeur demande à OP d'écrire le leur. (Vous ne pouviez pas inventer!)Si vous pensez que votre code a plus de vérifications nulles qu'il ne devrait en avoir, voici quelques conseils de Herb Sutter sur les pointeurs, les pointeurs intelligents, les références et le passage de paramètres. herbsutter.com/2013/06/05/…
@Eljay, merci pour le lien Herb Sutter - nous prendrons évidemment ces points à bord pour des scénarios pertinents.
Voir aussi stackoverflow.com/q/55661068/94687 , où l'auteur "essaie de créer un code unique_ptr "et stackoverflow.com/a/46303030/94687 , qui montre comment" un
value_ptr
peut être écrit sans être nullable. (Notez qu'il y a des coûts supplémentaires lors du déplacement.) "