0

Consider the following code:

#include <functional>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <time.h>

struct Foo {
  Foo(int x) : x(x) {}
  void func1() const { printf("Foo::func1 x=%d\n", x); }
  void func2() const { printf("Foo::func2 x=%d\n", x); }
  int x;
  char junk[64];
};

struct Bar {
  Bar(int x) : x(x) {}
  void func3() const { printf("Bar::func3 x=%d\n", x); }
  int x;
  char junk[64];
};

void loop(std::function<void()>& g) {
  for (int i=0; i<10; ++i) {
    switch (rand()%3) {
      case 0:
        {
          Foo foo(3);
          g = std::bind(&Foo::func1, foo);
          break;
        }
      case 1:
        {
          Foo foo(4);
          g = std::bind(&Foo::func2, foo);
          break;
        }
      case 2:
        {
          Bar bar(5);
          g = std::bind(&Bar::func3, bar);
          break;
        }
      default: break;
    }
  }
}

int main() {
  srand(time(NULL));
  std::function<void()> g;
  loop(g);
  g();
  return 0;
}

This code will execute exactly one of Foo::func1(), Foo::func2(), or Bar::func3(), using a copied instance of an object (that has fallen out of scope).

What I'd like to change with this code is to remove the dynamic allocation inside loop(). To this end, I tried making a wrapper around std::function, like so:

template <typename _Signature> class my_function;

template <typename R, typename... Args>
class my_function<R(Args...)> {
public:
  template<typename T, typename FUNC>
    void bind(FUNC&& func, const T& t) {
      static_assert(sizeof(t) <= sizeof(obj_buffer), "insufficient obj_buffer size");
      static_assert(std::is_trivially_destructible<T>::value, "memory leak");

      auto p = new(obj_buffer) T(t);  // placement new
      auto ref = std::ref(*p);
      f = std::bind(func, ref /* how do I forward variable # of placeholders here??? */);
    }

  R operator()(Args&& ...args) const { return f(std::forward<Args>(args)...); }

private:
  char obj_buffer[1024];
  std::function<R(Args...)> f;
};

I then changed the declaration of g from std::function<void()> to my_function<void()> and replaced g = std::bind(x,y) statements with g.bind(x,y) ones inside loop(). The code compiles and works as expected.

I have 2 separate questions:

  1. Does the standard guarantee that we avoid dynamic memory allocation here? User Jarod42 told me that it does in a very similar setting using an anonymous lambda, but I'm not sure if the same holds here with std::bind.

  2. How do I instantiate f inside my bind() function when I have a nonempty template parameter pack argument Args? My code as is won't compile for nonempty Args.

Community
  • 1
  • 1
dshin
  • 2,354
  • 19
  • 29
  • 3
    before we get into answering the questions, there's the small matter of the alignment guarantees of a char array. Suggest you look into http://en.cppreference.com/w/cpp/types/aligned_storage – Richard Hodges Jan 04 '16 at 21:28
  • Thanks for the tip. Would you mind sharing the list of benefits that alignment would bring here, for my educational benefit? I can think of performance of copy construction, and perhaps whether the small object allocation guarantee kicks in or not (obviously both hugely important in my context). Are there other considerations here? – dshin Jan 04 '16 at 21:54
  • 2
    Start from the fact that the placement new is UB if the buffer is not suitably aligned. – T.C. Jan 04 '16 at 22:41
  • `std::bind` does the copy of these arguments, so it is not these that you have to placement new, but the return of the `bind`. it is `std::function` which may allocate for non function pointer/ non reference_wrapper. – Jarod42 Jan 05 '16 at 01:52
  • @Jarod42 Sorry, I'm having trouble making sense of your comment. The copy of `T` needs a home. Wherever it lives, it must be placement-new'ed there if we are to avoid dynamic allocation, right? – dshin Jan 05 '16 at 14:05
  • `bind` return mostly a `struct` with `T`. It is `std::function` which does allocation (unless optimization for function pointer and reference wrapper) for the type erasure. – Jarod42 Jan 05 '16 at 17:18
  • In this context, it should store a reference to `T`, not a `T`. I verified by clearing `obj_buffer` between `bind()` and `f()`. So I think the question is whether std::bind(func, std::reference_wrapper) might incur dynamic allocation or whether the standard guarantees it won't. – dshin Jan 05 '16 at 17:31
  • Indeed you use `std::ref` for bind argument, but why using `std::ref` for `bind` if you have to keep the reference valid ? – Jarod42 Jan 05 '16 at 18:11
  • Sorry, I misunderstood the mechanics of `std::bind`. I understand what you mean now. – dshin Jan 05 '16 at 22:22

0 Answers0