Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The lengths some go to avoid just using a bog-standard virtual function.


I actually used the "virtual function" approach earlier in SumatraPDF.

The problem with that is that for every type of callback you need to create a base class and then create a derived function for every unique use.

That's a lot of classes to write.

Consider this (from memory so please ignore syntax errors, if any):

    class ThreadBase {
       virtual void Run();
       // ...
    }

    class MyThread : ThreadBase {
       MyData* myData;
       void Run() override;
       // ...
    }
    StartThread(new MyThread());
compared to:

    HANDLE StartThread(const Func0&, const char* threadName = nullptr);    
    auto fn = MkFunc0(InstallerThread, &gCliNew);
    StartThread(fn, "InstallerThread");

I would have to create a base class for every unique type of the callback and then for every caller possibly a new class deriving.

This is replaced by Func0 or Func1<T>. No new classes, much less typing. And less typing is better programming ergonomics.

std::function arguably has slightly better ergonomics but higher cost on 3 dimension (runtime, compilation time, understandability).

In retrospect Func0 and Func1 seem trivial but it took me years of trying other approaches to arrive at insight needed to create them.


>> I would have to create a base class for every unique type of the callback and then for every caller possibly a new class deriving.

An interface declaration is, like, two lines. And a single receiver can implement multiple interfaces. In exchange, the debugger gets a lot more useful. Plus it ensures the lifetime of the "callback" and the "context" are tightly-coupled, so you don't have to worry about intersecting use-after-frees.


You could do:

    template<class R, class... Args>
    struct FnBase {
       virtual R operator()(Args...) = 0;
    };

    class MyThread : FnBase<void> { ... };


This is pithy and clever, but in exchange makes the code less obviously self-documenting (implementation details creeping into type declarations) and complicates implementing multiple interfaces on the same receiver.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: