17 votes

Utiliser les lambdas C++11 de manière asynchrone et sûre

J'ai adopté le C++11 après avoir travaillé en Objective-C, et j'ai du mal à accepter la différence de sémantique de capture entre les lambdas du C++11 et les "blocs" de l'Objective-C. (Cf. (Voir aquí pour une comparaison).

En Objective-C, comme en C++, le self / this Le pointeur est implicitement capturé si vous faites référence à une variable membre. Mais comme tous les objets en Objective-C sont effectivement des "pointeurs partagés", pour reprendre la terminologie du C++, vous pouvez le faire :

doSomethingAsynchronously(^{
  someMember_ = 42;
});

... et vous avez la garantie que l'objet dont vous accédez au membre sera vivant lorsque le bloc sera exécuté. Vous n'avez pas à y penser. L'équivalent en C++ semble être quelque chose comme :

// I'm assuming here that `this` derives from std::enable_shared_from_this and 
// is already owned by some shared_ptr.
auto strongThis = shared_from_this();

doSomethingAsynchronously([strongThis, this] {
  someMember_ = 42;   // safe, as the lambda holds a reference to this
                      // via shared_ptr.
});

Ici, vous devez vous rappeler de capturer le shared_ptr en plus du this pointer. Existe-t-il une méthode moins sujette aux erreurs pour y parvenir ?

8voto

ecatmur Points 64173

L'un des principes fondateurs du C++ est que l'on ne paie pas pour ce que l'on n'utilise pas. Cela signifie dans ce cas que les contextes où l'on prend un shared_ptr a this n'est pas nécessaire ne devrait pas entraîner de surcharge de comptage de références. Cela signifie également que cela ne doit pas se produire automatiquement, même si c'est une fonctionnalité de la fonction enable_shared_from_this car on peut vouloir passer un lambda de courte durée à un algorithme ( for_each ), auquel cas la lambda ne dépasse pas sa portée.

Je suggérerais d'adapter le modèle lambda-wrapper dans ce cas, il est utilisé pour move la capture d'un objet de grande taille ( Comment capturer std::unique_ptr "par déplacement" pour un lambda dans std::for_each ), mais elle peut également être utilisée pour la capture partagée de this :

template<typename T, typename F>
class shared_this_lambda {
  std::shared_ptr<T> t;  // just for lifetime
  F f;
public:
  shared_this_lambda(std::shared_ptr<T> t, F f): t(t), f(f) {}
  template<class... Args>
  auto operator()(Args &&...args)
  -> decltype(this->f(std::forward<Args>(args)...)) {
    return f(std::forward<Args>(args)...);
  }
};

template<typename T>
struct enable_shared_this_lambda {
  static_assert(std::is_base_of<std::enable_shared_from_this<T>, T>::value,
    "T must inherit enable_shared_from_this<T>");
  template<typename F>
  auto make_shared_this_lambda(F f) -> shared_this_lambda<T, F> {
    return shared_this_lambda<T, F>(
      static_cast<T *>(this)->shared_from_this(), f);
  }
  template<typename F>
  auto make_shared_this_lambda(F f) const -> shared_this_lambda<const T, F> {
    return shared_this_lambda<const T, F>(
      static_cast<const T *>(this)->shared_from_this(), f);
  }
};

Utilisation par héritage enable_shared_this_lambda en plus de enable_shared_from_this vous pouvez alors demander explicitement à ce que les lambdas à longue durée de vie prennent un fichier partagé this :

doSomethingAsynchronously(make_shared_this_lambda([this] {
  someMember_ = 42;
}));

6voto

JE42 Points 657

Booster les utilisations :

auto self(shared_from_this());
auto l = [this, self] { do(); };

Mentionné ici : Quelle est la raison de l'utilisation de la variable auto self(shared_from_this()) dans la fonction lambda ?

2voto

En fait, il y a une seule bonne réponse à ce problème. La réponse a exactement le même effet que la liaison avec shared_from_this() (comme lorsque vous le faites avec boost::asio::io_service ). Réfléchissez-y : qu'est-ce que la liaison avec shared_from_this() faire ? Il remplace simplement this . Alors, qu'est-ce qui vous empêche de remplacer this con shared_from_this() totalement ?

En suivant votre exemple, que j'ai mis à jour pour rendre la différence plus claire, au lieu de cela :

auto strongThis = shared_from_this();

doSomethingAsynchronously([strongThis, this] () {
  this->someMember_ = 42; //here, you're using `this`... that's wrong!
});

Faites-le :

auto strongThis = shared_from_this();

doSomethingAsynchronously([strongThis] () //notice, you're not passing `this`!
{
  strongThis->someMember_ = 42;            
});

Le seul coût ici est que vous allez devoir tout préfixer avec strongThis-> . Mais c'est la manière la plus significative de le faire.

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