-1

I'd like to fill in the store() and launch() methods in the below code. The important detail which captures the spirit of the problem is that the object foo declared in main() no longer exists at the time we call launch(). How can I do this?

#include <cstdio>
#include <cstring>
#include <type_traits>

template<typename T, typename U=
  typename std::enable_if<std::is_trivially_copyable<T>::value,T>::type>
struct Launchable {
  void launch() { /* some code here */ }

  T t;
  // other members as needed to support DelayedLauncher 
};

class DelayedLauncher {
public:
  template<typename T>
    void store(const Launchable<T>& t) {
      // copy-construct/memcpy t into some storage
    }

  void launch() const {
    // call t.launch(), where t is (a copy of) the last value passed into store()
  }

  // other members as needed
};

int main() {
  DelayedLauncher launcher;
  {
    Launchable<int> foo;
    launcher.store(foo);
  }
  launcher.launch();  // calls foo.launch()
  return 0;
}

Note that if we only had a fixed set of N types to pass into store(), we could achieve the desired functionality by declaring N Launchable<T> fields and N non-template store() methods, one for each type, along with an enum field whose value is use in a switch statement in the launch() method. But I'm looking for an implementation of DelayedLauncher that will not need modification as more Launchable types are added.

dshin
  • 2,354
  • 19
  • 29
  • 1
    I don't understand why you can't simply add a member of the `Launchable` type to `DelayedLauncher`, and implement `store` by calling `operator=` on that member. – Violet Giraffe Jan 01 '16 at 21:04
  • If `Launchable` doesn't implement an assignment operator, you can use `unique_ptr` instead. – Violet Giraffe Jan 01 '16 at 21:06
  • @VioletGiraffe I need my class to support `Launchable`, `Launchable`, etc. – dshin Jan 01 '16 at 21:12
  • Perhaps I misunderstand, but wouldn't your suggestion violate the requirement in my last paragraph? "But I'm looking for an implementation of `DelayedLauncher` that will not need modification as more `Launchable` types are added." – dshin Jan 01 '16 at 21:15
  • 1
    Does `DelayedLauncher` need to be a non-template class? – Mark B Jan 01 '16 at 21:19
  • Sorry, I did not realize `DelayedLauncher` is not a template. Now I understand the question. – Violet Giraffe Jan 01 '16 at 21:19
  • Do you realize that `DelayedLauncher` looks a hell of a lot like `std::function`, functionality-wise? – Violet Giraffe Jan 01 '16 at 21:20
  • @MarkB Yes, `DelayedLauncher` must be a non-template class. – dshin Jan 01 '16 at 21:23
  • @VioletGiraffe I had never used `std::function` before so I didn't know to draw the connection. Jarod42's solution makes the connection clear. – dshin Jan 01 '16 at 21:47
  • Please see my answer, I have reproduced your `main()` using only `std::function` and no custom classes. – Violet Giraffe Jan 01 '16 at 21:56

3 Answers3

2

using std::function:

class DelayedLauncher {
public:
  template<typename T>
    void store(const Launchable<T>& t) {
      f = [t]() {t.launch();};
    }

  void launch() const { f(); }

 private:
     std::function<void()> f;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Why so complicated? Surely, a lambda and/or `std::function` is unnecessary to fulfill the OP's task. Having said that, `Launchable` itself should probably be substituted for `std::function` entirely. – Violet Giraffe Jan 01 '16 at 21:09
  • 2
    @VioletGiraffe: As I understand OP's usage may store `Launchable` than `Launchable`, so we have to have some sort of type erasure (or take a clonable `ILaunchable`). – Jarod42 Jan 01 '16 at 21:13
  • Great, this did the trick. This seems mysterious to me - is there some sort of dynamic memory allocation under the hood? If so, where exactly does it happen? – dshin Jan 01 '16 at 21:40
  • All the magic is in `std::function`, which indeed would do one dynamic allocation to do its type erasure and save the lambda. – Jarod42 Jan 01 '16 at 21:50
  • I need `store()` to be as lightweight as possible. Is it possible to somehow supply my own allocator? – dshin Jan 01 '16 at 21:52
  • 1
    see [how-can-i-create-a-stdfunction-with-a-custom-allocator](http://stackoverflow.com/questions/21094052/how-can-i-create-a-stdfunction-with-a-custom-allocator) for custom allocator. – Jarod42 Jan 01 '16 at 21:59
  • @Jarod42 Thanks. Would you mind taking a look at my [solution](http://stackoverflow.com/a/34561684/543913)? It is my attempt at extending yours to one that avoids dynamic allocation. – dshin Jan 02 '16 at 01:24
1

You could give Launchable a base class with a virtual launch() and no template, and store pointers to that base class in Launcher::store.

EDIT: Adapted from @dshin's solution:

struct LaunchableBase {
    virtual void launch() = 0;
};

template<typename T, typename U=
  typename std::enable_if<std::is_trivially_copyable<T>::value,T>::type>
  struct Launchable : public LaunchableBase {
      virtual void launch() override { /* some code here */ }

      T t;
      // other members as needed to support DelayedLauncher 
  };

class DelayedLauncher {
    public:
        template<typename T>
            void store(const Launchable<T>& t) {
                static_assert(sizeof(t) <= sizeof(obj_buffer),
                        "insufficient obj_buffer size");
                static_assert(std::is_trivially_destructible<T>::value,
                        "leak would occur with current impl");
                p = new (obj_buffer) Launchable<T>(t);
            }

        void launch() const {
            p->launch();
        }

    private:
        char obj_buffer[1024];  // static_assert inside store() protects us from overflow
        LaunchableBase *p;
};
Gary Jackson
  • 565
  • 4
  • 18
  • The pointer may no longer be valid when it comes time to launch(). – dshin Jan 02 '16 at 03:38
  • @dshin You allocate the space and copy t in store(), just like your solution. Instead of making a closure around a local variable p, you just store it directly in a member named p and call p->launch() from DelayedLauncher::launch(). The idea is to use the abstract untemplated base of Launchable to erase the type of Launchable. Using a lambda is a little more succinct, though. – Gary Jackson Jan 02 '16 at 04:10
  • I see. Still, I think my solution is preferable due to the fact that it incurs no dynamic allocation overhead. – dshin Jan 02 '16 at 05:53
  • @dshin FWIW, you'd allocate p in the exact same way you do it in your solution, and std::function has its own overhead that's at least as bad as a vtable lookup. On gcc 4.9, vtable is 6-7x faster than std::function if it can't optimize the lookup away, and about 75 thousand times as fast if it can. The compiler basically can't optimize std::function. – Gary Jackson Jan 02 '16 at 06:16
  • Incidentally, clang 3.7 performs unbelievably poorly in all cases compared to gcc for the toy program I wrote to compare vtable and std::function dispatch. – Gary Jackson Jan 02 '16 at 06:23
  • Are you talking about the cost of `store()`, or the cost of `launch()`? I need a fast `store()`, and don't care about the cost of `launch()`. Also, in my application there will be thousands of different types `T` of varying sizes of up to ~1000 bytes. `store()` might be called hundreds of times rapidly in succession before hitting a `launch()`. – dshin Jan 02 '16 at 13:05
  • I've updated my answer to better communicate my intent. Hopefully that answers your questions. – Gary Jackson Jan 02 '16 at 16:10
  • Aha, I see. Yes, that works and avoids dynamic allocation, although there is a tradeoff of slower construction time of `Launchable` objects (due to vtable initialization) against the faster `launch()`. In my particular use case the tradeoff will not be worth it, but I could easily see your solution being preferable in a different context. I've changed my downvote to an upvote. – dshin Jan 02 '16 at 16:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99540/discussion-between-dshin-and-gary-jackson). – dshin Jan 02 '16 at 16:40
0

I believe this variant of Jarod42's solution will avoid dynamic allocation, although I would appreciate if someone could confirm that this will work the way I think it will:

class DelayedLauncher {
public:
  template<typename T>
    void store(const Launchable<T>& t) {
      static_assert(sizeof(t) <= sizeof(obj_buffer),
        "insufficient obj_buffer size");
      static_assert(std::is_trivially_destructible<T>::value,
        "leak would occur with current impl");
      auto p = new (obj_buffer) Launchable<T>(t);
      auto ref = std::ref(*p);
      f = [=]() {ref.get().launch();};
    }

  void launch() const {
    f();
  }

private:
  char obj_buffer[1024];  // static_assert inside store() protects us from overflow
  std::function<void()> f;
};

I believe it should work because the resources I've looked at indicate that std::function implementations typically have a "small capture" optimization, only triggering a dynamic allocation if the total size of the captured data exceeds some threshold.


EDIT: I replaced my code with a version provided by Jarod42 in the comments. The standard guarantees the above implementation will not trigger dynamic allocation.

Community
  • 1
  • 1
dshin
  • 2,354
  • 19
  • 29
  • You have to use placement new instead of `memcpy` and you don't remove allocation done by `std::function`. – Jarod42 Jan 02 '16 at 01:41
  • Doesn't the fact that `std::is_trivially_copyable` mean that `memcpy` and placement new are equivalent? – dshin Jan 02 '16 at 01:48
  • I miss that part, indeed, but here you don't copy, you create a new object. I'm not sure if interpreting buffer as object in this code is not UB. – Jarod42 Jan 02 '16 at 01:54
  • I'd be interested to see if it is UB for some reason. But to me the bigger question is why you say `std::function` still needs to allocate. I'm capturing a total of 8 bytes, and the sources I'm using ([example](http://www.drdobbs.com/cpp/efficient-use-of-lambda-expressions-and/232500059?pgno=2)) seem to indicate that because 8 is small, those bytes can be stored statically. I'm finding the `std::function` header a bit hard to parse but I see a bool `__stored_locally` which appears to be related? – dshin Jan 02 '16 at 02:12
  • Small object optimization is guaranty for `std::reference_wrapper` and function pointer. it might be done in your case, but without guaranty. – Jarod42 Jan 02 '16 at 02:21
  • 1
    something like [that](http://coliru.stacked-crooked.com/a/a391bfd190c51afc) should be better. – Jarod42 Jan 02 '16 at 02:37
  • Great, thank you for your help. This now gets me everything I want. I still have trouble imagining why small object optimization might not kick in for a pointer if it is guaranteed for a `std::reference_wrapper`. Maybe in practice it always will? – dshin Jan 02 '16 at 02:57
  • In practice, it will probably use this optimization I think. – Jarod42 Jan 02 '16 at 10:51
  • @Jarod42 I've asked a follow-up question [here](http://stackoverflow.com/q/34599965/543913), basically asking if the same guarantee applies for std::bind() as does here with anonymous lambda. I'd appreciate your expertise there if you wouldn't mind. Thanks! – dshin Jan 04 '16 at 21:15