2 votes

Signature de la fonction pour le déplacement d'un unique_ptr qui a potentiellement échoué ?

Supposons que j'écrive un enqueue() qui prend en compte un unique_ptr mais je ne veux en revendiquer la propriété que lorsque enqueue renvoie un succès. Si la file d'attente est pleine, je veux laisser l'élément unique_ptr intacte (l'utilisateur peut réessayer plus tard avec le même élément)

bool enqueue(std::unique_ptr&& item){
  if(!vector.full()){
    vector.emplace(std::move(item));
    return true;
  }
  return false;
}
// usage
auto item_ptr = make_unique<>();
while(!enqueue(std::move(item_ptr))){
// item_ptr is not moved
}

Je peux également définir la fonction de manière à ce qu'elle prenne une référence à une valeur lvalue à la place

bool enqueue(std::unique_ptr& item)
while(!enqueue(item_ptr)){
// item_ptr is not moved
}

Je ne sais pas lequel choisir, ils me semblent tous un peu anti-motifs, car habituellement, ils ne sont pas très bien conçus, et je ne sais pas si j'ai envie de les choisir. std::move indique la suppression d'un unique_ptr (la plupart du temps, je travaille avec une fonction qui prend unique_ptr par valeur), peut-être existe-t-il une meilleure solution ?

2voto

Scheff Points 8113

La première version avec la référence RValue accepte une valeur temporaire. Ainsi, si le déplacement n'a pas eu lieu, vous avez une instance dans un fichier temporaire std::unique_ptr qui sera bientôt supprimée. C'est ce qui me fait douter du bien-fondé de ce choix (ou de la possibilité d'obtenir des effets surprenants).

Il faut au moins en être conscient.

Concernant la question

que se passe-t-il si l'on fait while (!enqueue(make_unique<>()))...

J'ai fait un MCVE (juste pour être sûr) :

#include <memory>
#include <iostream>

struct Test {
  Test() { std::cout << "Test::Test()\n"; }
  ~Test() { std::cout << "Test::~Test()\n"; }
};

bool enqueue(std::unique_ptr<Test>&& item)
{
  return false;
}

int main()
{
  for (int i = 0; i < 3 && !enqueue(std::make_unique<Test>()); ++i);
}

Sortie :

Test::Test()
Test::~Test()
Test::Test()
Test::~Test()
Test::Test()
Test::~Test()

Démonstration sur coliru

S'il est destiné à prévenir les "abus" et à garantir que enqueue() n'est pas appelé pour les temporaires, la version RValue pourrait être abandonnée.

1voto

Zan Lynx Points 23100

Vous pouvez renvoyer un unique_ptr à partir de votre fonction. En cas d'échec, renvoyez l'original. Si elle a réussi, elle renvoie une unique_ptr vide.

On pourrait alors l'appeler ainsi :

#include <iostream>
#include <memory>
#include <vector>

int counter = 10;

template <typename T> std::unique_ptr<T> enqueue(std::unique_ptr<T> p) {
  if (--counter == 0)
    return std::unique_ptr<T>();
  return p;
}

int main() {
  auto item_ptr = std::make_unique<int>(8);
  while (item_ptr = enqueue(std::move(item_ptr))) {
    std::cout << "looping\n";
  }
  std::cout << "moved\n";
  return 0;
}

En fait, je ferais mieux d'aller l'essayer. Oui, ma première idée avait un bug. L'arrachage de ce ! de la boucle while. Testez toujours :-)

0voto

StoryTeller Points 6139

L'état d'esprit que j'aime avoir sur std::move est que ce n'est que moi qui indique que je suis d'accord pour que l'objet soit modifié. Il ne s'agit pas de provoquer la suppression, mais d'autoriser la modification de l'objet. potentiellement des choses destructrices à se produire.

Votre première option me semble correcte. Les appelants doivent explicitement donner leur permission en marquant leurs valeurs l avec std::move et ils peuvent vérifier si quelque chose s'est effectivement produit en inspectant la valeur de retour de la fonction. Ils n'auront donc pas d'objets auxquels ils tiennent qui disparaissent soudainement. C'est une bonne chose assez IMO.

Mais comme autre réponse Toutefois, cette version offre également la possibilité de passer une unique_ptr . Si le travail inutile dans ce cas vous dérange, nous devrions probablement adopter une autre approche.

Une ligne de conduite que j'aime parfois reprendre du guide de style de Google est que les paramètres [input/]output sont transmis par un pointeur brut non propriétaire. Cela les limite essentiellement à des valeurs l, tout en conservant le caractère explicite nécessaire pour permettre leur modification. Vous pouvez donc faire ceci :

bool enqueue(std::unique_ptr<T>* item){
  if(!vector.full()){
    vector.emplace(std::move(*item));
    return true;
  }
  return false;
}

// usage
auto item_ptr = make_unique<T>();
while(!enqueue(&item_ptr)){
// item_ptr is not modified
}

Dans le cadre d'un tel guide de style, l'opérateur address-of & a la même fonction que std::move . C'est nous qui disons à la fonction : "Allez-y, modifiez-la si nécessaire".

0voto

Cette solution me semble un peu délicate, car vous passez l'élément unique_ptr mais il peut être revendiqué ou non, et il est donc nécessaire de gérer ce type de comportement : par exemple, renvoyer un fichier unique_ptr en cas de succès ou de code d'erreur/de succès, en effectuant des contrôles redondants ou difficiles à comprendre, etc. Je pense que ce que vous pouvez faire est de vérifier si votre file d'attente a de l'espace libre à l'extérieur, avec l'aide de la méthode full() ou quelque chose comme ça. Votre code ressemblera alors à quelque chose comme ça :

if(!full()) {
    enqueue(std::move(...));
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X