Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to safely use callbacks when the bound function could be deleted

In the following code, we are creating an object, bind one function and call it before then after deleting the object.

This obviously leads to a segmentation fault as the underlying object was used after deletion.

In the context of a library providing callbacks for asynchronous data, how are we supposed to prevent callback functions to point to a nullptr?

You can test at cpp.sh/5ubbg

#include <memory>
#include <functional>
#include <iostream>

class CallbackContainer {
 public:
  std::string data_;
  CallbackContainer(std::string data): data_(data) {}
  ~CallbackContainer() {}
  void rawTest(const std::string& some_data);
};

void CallbackContainer::rawTest(const std::string& some_data) {
  std::cout << data_ << " " << some_data << std::endl;
}

int main(int /* argc */, char const** /* argv */) {
  std::unique_ptr<CallbackContainer> container;
  container.reset(new CallbackContainer("Internal data"));

  auto callback = std::bind(&CallbackContainer::rawTest, container.get(), std::placeholders::_1);
  callback("Before");
  std::cout << &callback << std::endl;
  container.reset();
  std::cout << &callback << std::endl;
  callback("After");
  return 0;
}

Returns:

> Internal data Before 
> 0x7178a3bf6570 
> 0x7178a3bf6570 
> Error launching program (Segmentation fault)
like image 771
Alexis Paques Avatar asked Sep 16 '25 06:09

Alexis Paques


1 Answers

If you can share ownership, do this:

int main(int /* argc */, char const** /* argv */) {
  std::shared_ptr<CallbackContainer> container; // shared pointer
  container.reset(new CallbackContainer("Internal data"));
  // shared with functor
  auto callback = std::bind(&CallbackContainer::rawTest, container, std::placeholders::_1); 
  callback("Before");
  std::cout << &callback << std::endl;
  container.reset();
  std::cout << &callback << std::endl;
  callback("After");
  return 0;
}

If not, you should somehow pass invalidity to function object explicitly. This assumes that you know when container is deleted, and manually invalidate explicitly before that:

int main(int /* argc */, char const** /* argv */) {
  std::unique_ptr<CallbackContainer> container;
  container.reset(new CallbackContainer("Internal data"));
  std::atomic<CallbackContainer*> container_raw(container.get());
  auto callback = [&container_raw] (std::string data)
  {
    if (auto c = container_raw.load())
      c->rawTest(data);
  };
  callback("Before");
  std::cout << &callback << std::endl;
  container_raw.store(nullptr);
  container.reset();
  std::cout << &callback << std::endl;
  callback("After");
  return 0;
}

For asio cases, usually shared_from_this() is used, like std::bind(&MyClass::MyMemFunc, shared_from_this(), ptr);

like image 180
Alex Guteniev Avatar answered Sep 17 '25 21:09

Alex Guteniev