4

This question is sort of an extension to this one, as it's a similar problem but doesn't fully answer my problem as much as I would like (not to mention the problem in that question doesn't involve threads/multi-threading).

The Problem

Like the title suggests, I've encountered a specific problem regarding lambdas and std::threads where my background knowledge can't help me. I'm essentially trying to store a std::function, which contains a member function, in a static class to be called later in a separate std::thread. The problem is that this member function needs the this instance pointer by reference because this member function modifies data members of the same class, but the this pointer passed by reference into the lambda is invalidated/destroyed by the time the member function is called, which causes what seems like undefined behavior. This problem is actually somewhat talked about in proposal document P0018R3, where it talks about lambdas and concurrency.

Semi-Minimal Working Example

//Function class
struct FuncWrapper {
    //random data members
    std::string name;
    std::string description;
    //I still have this problem even if this member is const
    std::function<void()> f;
};
class FuncHolder {
public:
    //For the FuncWrapper argument, I've tried pass by reference, const-reference, rvalue, etc. doesn't make a difference in terms of the problem (as I expect)
    //add a FuncWrapper object to the static holder
    static void add(FuncWrapper f) {
        return const_cast<std::vector<FuncWrapper>&>(funcs).push_back(f);
    }
    //Same here with the return
    //get a static FuncWrapper object by index (with bounds checking)
    static const std::function<void()>& get(size_t index) {
        return funcs.at(index);
    }
private:
    //C++17 inline static definition; If I weren't using it I would do it the non-inline way
    const static inline std::vector<FuncWrapper> funcs;
};
//Data member class
struct Base {
    //This is meant to be like a constexpr; its unique for each derived type
    inline virtual const char* const getName() const = 0;
protected:
    //This will be important for a solution I tried
    const size_t storedIdx = 0;
};
class Derived : public Base {
public:
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [&](){increment(5);} //This is the problem-causing statement
        });
        FuncHolder::add({"add1", "adds 1", 
            std::bind(&FuncHolder::increment, 1) //This syntax also causes the same problem
        }); 
    }
    inline const char* const getName() const override {
        return "Derived";
    }
    void increment(int amount){
         //Do some stuff...
         privMember += amount;
         //Do some other stuff..
    }
private:
    int privMember = 0;
};

//Class to hold the static instances of the different derived type
class BaseHolder {
public:
    //make a new object instace in the static vector
    template<class DerivedTy>
    static void init(const DerivedTy& d){
        static_assert(std::is_base_of_v<Base, DerivedTy>); //make sure it's actually derived
        size_t idx = baseVec.size(); //get the index of the object to be added
        const_cast<std::vector<std::unique_ptr<Base>>&>(baseVec).emplace_back(std::make_unique<DerivedTy>(d)); //forward a new unique_ptr object of derived type
        const_cast<size_t&>(baseVec.at(idx)->storedIdx) = idx; //store the object's index in the object itself
    }
    ///This function is used later for one of the solutions I tried; it goes unused for now
    ///So, assume the passed size_t is always correct in this example
    ///There's probably a better way of doing this, but ignore it for the purposes of the example
    //get a casted reference to the correct base pointer
    template<class DerivedTy>
    static DerivedTy& getInstance(size_t derivedIdx){
        return *(static_cast<DerivedTy*>(baseVec.at(derivedIdx).get()));
    }
private:
    //C++17 inline static again
    const static inline std::vector<std::unique_ptr<Base>> baseVec{};
};
int main() {
    BaseHolder::init(Derived()); //add a new object to the static holder
    //Do stuff...
    std::thread runFunc([&](){
        FuncHolder::Get(0)(); //Undefined behavior invoked here; *this pointer used in the function being called is already destroyed
    });
    //Main thread stuff...
    runFunc.join();
    return 0;
}

It may not be a super minimal example, but I wanted to highlight the important details (such as how the function is stored and the class(es) that call them) so that it's clear how the problem originates.
There's also a few possibly unrelated yet important parts of the design to point out.

  1. it's intended for there to be many classes/types deriving the Base class (i.e. Derived1, Derived2, etc.), but there will only be one instance of each of those derived classes; Hence why all members of the BaseHolder class are static. So if this design does need to be reworked, keep this in mind (though honestly maybe this could be implemented in a better way than it is now, but that may be unrelated to the problem).
  2. It may be instinctual to make the BaseHolder class be templated and just pass the classes/types that I want it to hold to its template at compile time (and thus use something like a tuple instead of a vector), but I didn't do that on purpose because I may need to add more Derived types later in runtime.
  3. I can't really change the template type of f (the std::function<>) because I may need to pass different functions with their own return type and arguments (which is why I use a lambda sometimes and std::bind at times when I just want the callable to a be a function with void return type). In order to accomplish this I just make it a std::function<void()>
  4. The overall goal of this design is to statically call and invoke a function (as if it were triggered by an event) that has been constructed before being called and has the ability to modify a given class (specifically the class its constructed in - Derived in this case).

Problem Origin

Looking into this problem, I know that the this pointer captured by reference in a lambda can be invalidated by the time the lambda runs in a different thread. Using my debugger, I seems like the this pointer was destroyed by the time the lambda was being constructed in the constructor of Derived, which went against my previous knowledge, so I can't be 100% sure this is what's going on; The debugger showed that the entire this instance of Derived was filled with junk values or was unreadable

Derived(){
    FuncHolder::add({"add5", "adds 5", //`this` pointer is fine here
       [&](){increment(5);} //`this` pointer is filled with junk and pointing to a different random address
    });
    //...
}

I'm more sure though about the undefined behavior when the lambda/function is invoked/ran due to its seemingly destroyed this instance of Derived pointer. I get different exceptions each time, from different files, and sometimes just get a flat out access read access violation and what not sometimes. The debugger also can't read the memory of the this pointer of the lambda when it comes around to invoking it; All look like signs of a destroyed pointer.
I've also dealt with this type of problem before in lambdas and know what to do when std::threads are not involved, but the threads seem to complicate things (I'll explain that more later).

What I've Tried

Capture by value

The easiest solution would be to just make the lambda capture the this pointer of Derived by value (as mentioned in both the answer to the aforementioned question and proposal document P0018R3) since I'm using C++17. The proposal document even mentions how capturing this by value is necessary for concurrent applications such as threading:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [&, *this](){increment(5);} //Capture *this by value (C++17); it's thread-safe now
    });
    //...
}

The problem with this is, like I said, the functions passed into/captured by the lambda need to modify data members of the class; if I capture this by value, the function is just modifying a copy of the Derived instance, instead of the intended one.

Use Static Instance

Okay, if each derived class is only supposed to have one static instance, and there's a static holder of derived classes, why not just use the static instance in the lambda and modify that instance directly?:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [=](){BaseHolder::getInstance(storedIdx)::increment(5);} //use static instance in lambda; Again assume the passed index is always correct for this example
    });
    //...
}

This might look good on paper, but the problem is that the getInstance() is being called in the constructor, before the actual instance is created using the constructor. Specifically, the Derived() constructor is called in BaseHolder::init(Derived()) where init tries to create the instance in vector in the first place; But, the vector is accessed in the Derived() constructor, which is called before init is.

Pass Static Instance to Member Function

Another answer in the aforementioned question says to change the function in the lambda to have a argument that takes an instance of its class. In our example, it would look something like this:

class Derived : public Base {
public:
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [&](){increment(BaseHolder::getInstance(storedIdx), 5);} //Pass the static instance to the actual function
        });
        //...
    }
    //rest of the class...
    void increment(Derived& instance, int amount){
         //Do some stuff...
         instance.privMember += amount;
         //Do some other stuff..
    }
private:
    int privMember = 0;
};

But this as the same problem as the previous attempted solution (using the static instance in the lambda): the static instance isn't created yet because it's calling the constructor accessing the instance to create it.

shared_ptr of this (directly)

A solution mentioned more than once in the aforementioned question was to make and use a shared_ptr (or any smart pointer for that matter) of this to extend its lifetime and what not (though the answers did not go into depth on how to implement it). The quick-and-dirty way to do this is directly:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [self=std::shared_ptr<Derived>()](){self->increment(5);} //pass a shared_ptr of *this; syntax can differ
    });
    //...
}

The problem with this is that you get a std::bad_weak_ptr exception, as doing it this way is probably incorrect anyway (or at least I assume).

shared_ptr of this (std::enable_shared_from_this<T>)

The solution in this blog post, and the solution I usually use when threads are not involved, is to make use of std::enable_shared_from_this<T>::shared_from_this to capture a proper shared_ptr of this:

class Derived : public Base, public std::enable_shared_from_this<Derived> {
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [self=shared_from_this()](){self->increment(5);} //pass a shared_ptr of *this
        });
        //...
    }
    //rest of class...
}

This looks good on paper, and doesn't really cause any exceptions, but it doesn't seem to change anything; The problem still remains and it seems no different than just capturing this by reference normally.

Conclusion

Can I prevent the destruction/invalidation of the this pointer of the derived class in the lambda by the time it's called in another thread? If not, what's the right way to do what I am trying to achieve? I.e. how could I rework the design so it functions properly while still keeping my design principles preserved?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Abob
  • 751
  • 5
  • 27
  • shared_ptr approach should work, but not through shared_from_this, use std::make_shared to create an instance of your class then capture the shared pointer in you lambda that should extend the life cycle of your object to the duration of the call on the other thread. – Pepijn Kramer Nov 06 '21 at 06:28
  • 1
    Pass the instance's raw pointer by value, and it's `weak_ptr` from `shared_from_this`. Use `weak_ptr` to check the instance's validation and lock it before accessing raw pointer. See how Boost.Signals2 manages to do this. [Doc](https://www.boost.org/doc/libs/1_61_0/doc/html/signals2/tutorial.html#signals2.tutorial.connection-management), 3rd code block. – 김선달 Nov 06 '21 at 06:34
  • @PepijnKramer Do you mean by doing `self = std::make_shared(*this)` in the lambda capture list? I tried that and according to the debugger, on construction, the `self` object is fine and a valid pointer, but once the lambda is ran the `self` object is invalid and filled with junk values again. It's verified by the fact that `privMember` isn't actually incremeneted. Basically, the effect is the same as just sending `this` by reference. – Abob Nov 06 '21 at 06:41
  • @김선달 Could you be more specific or show an example? Assuming I'm looking at the right code block, I don't see how `deliverNews.connect(signal_type::slot_type(&NewsMessageArea::displayNews, newsMessageArea.get(), _1).track(newsMessageArea));` fully explains what you mean – Abob Nov 06 '21 at 06:44
  • 1
    Maybe like [this](https://godbolt.org/z/eraE93WT4) ? – 김선달 Nov 06 '21 at 06:55
  • After a more detailed reading of your question, I would say : Don't mix static instances with threads. As you see this will create life cycle issues by design. (In general I consider global variables/statics as design issues anyway, and yes there are exceptions). So your question should really be about WHAT you want to achieve. So we can help you with a more correct solution – Pepijn Kramer Nov 06 '21 at 07:23
  • @김선달 Okay I see what you mean now, I'll give that a try. How is this different from shared_from_this() though? – Abob Nov 06 '21 at 20:45
  • @PepijnKramer I already explained what I want to achieve in my question with my design goals/principles section – Abob Nov 07 '21 at 01:05
  • @김선달 I tried what you said, specifically in your example, and it "works" but not really. I did it the exact way in your example and put it into my implementation, and it can never acquire the instance (i.e. `!lock` is always true and the lambda just returns) and the `ptr` is never valid. I assumed it wouldn't really work because your example doesn't involve threads. – Abob Nov 08 '21 at 01:55
  • @Arastais It has nothing to do with threads. Check if you passed a temporary object. `weak_ptr` doesn't owns the instance. – 김선달 Nov 08 '21 at 03:59
  • @김선달 I took my exact code from my quetion and just changed the lambda in `Func` to be how your example is with `weak_from_this()`. The only difference is that I'm still using `unique_ptr` to store instances of `Derived` like in my code above – Abob Nov 08 '21 at 05:54
  • @Arastais `shared_from_this`, `weak_from_this` works only if when the instance is created with `shared_ptr`. You *must* use `shared_ptr` to track it's life. – 김선달 Nov 08 '21 at 06:49
  • @김선달 Doesn't make a difference, still can't get the instance and `!lock` is always true. I basically took my implementation above, changed `baseVec` to use `shared_ptr` instead, and then took your exact lambda in `register_to_func()` and replaced that with my lambda. – Abob Nov 08 '21 at 18:06
  • Your above code has many errors. `std::make_unique(d)` is copy-constructing a new instance from `d`, so the default constructor is not called. Default constructor is called for a temporary object `d`, which is destroyed when `BaseHolder::init` returns. That's why it crashes. – 김선달 Nov 09 '21 at 04:11
  • @김선달 It doesn't make sense for it to be copy-constructing. [the signature of make unique](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique) takes an rvalue and forwards it to the constructor of DerivedTy; std::forward also returns an rvalue, so why would it copy construct? If it is being destroyed anyway, then what is your solution? How could I fix the broad problem you're stating? – Abob Nov 09 '21 at 04:27
  • @Arastais That is not rvalue reference. It's called universal reference(by Scott Meyers). Your function is `static void init(const DerivedTy& d)`, thus resulting a copy constructor. I can tell you how to fix it, but it will solve just this very specific problem. It will be best for you to study C++ basics by reading [good books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list), online documents, etc. – 김선달 Nov 09 '21 at 06:23
  • @Arastais One rough solution can be changing `init` to accept `std::shared_ptr&&`, and move it to the `vector>`. But it seems like re-writing the whole logic would be better. – 김선달 Nov 09 '21 at 06:32
  • That’s not what I meant. Im passing a temporary to `init`, which gets passed to `make_unique` which only accepts rvalues. Theres no copying; I dont think ur `shared_ptr` suggestion won’t make a difference. [This](https://stackoverflow.com/questions/36102728/why-is-it-allowed-to-pass-r-values-by-const-reference-but-not-by-normal-reference) gives insight on that. – Abob Nov 09 '21 at 06:51
  • (Continued) But thats beside the point anyway, that doesn't affect my problem: the problem isn't how `Derived` is stored its that the `FuncHolder` lambda can’t hold a `this` pointer long enough. I will try your suggestion anyway, but if you know how to fully rewrite the logic please feel free to share your suggestion – Abob Nov 09 '21 at 06:51
  • That *is* making your problem. Continue in https://chat.stackoverflow.com/rooms/239019/discussion-between-arastais-and- – 김선달 Nov 09 '21 at 07:08

2 Answers2

4

I think there are many solutions that could work here, but it's not a "short" problem to describe. Here's my take on two approaches:

1 - Facilitate intentions

Looks like you need to share some state between an object and a lambda. Overriding lifetimes is complicated so do what you intend, and lift the common state into a shared pointer:

class Derived // : Base classes here
{
  std::shared_ptr<DerivedImpl> _impl;

public:
  /*
  Public interface delegates to _impl;
  */
};

this gives the ability to have 2 flavors of interaction with the shared state:

// 1. Keep the shared state alive from the lambda:
func = [impl = _impl]() { /* your shared pointer is valid */ };

// 2. Only use the shared state if the original holder is alive:
func = [impl = std::weak_ptr(_impl)]() {
  if (auto spt = impl.lock())
  {
    // Use the shared state
  }
};

After all the enable_shared_from_this approach didn't work for you because you didn't share the state to begin with. By storing the shared state internally you can keep the value semantics of your original design and facilitate uses where lifetime management gets complicated.

2 - Put state in a safe place

The safest way to guarantee that some state is "alive" at a certain point is to put it at a higher scope:

  1. Static storage / global scope
  2. A stack frame that is higher (or lower depending on your mental model of memory) than both users, the derived class and the lambda.

Having such a "store" would allow you to do "placement new" of your objects, to construct them without using the free store (heap). Then you pair this facility with reference counting, so that the last one to reference the derived object will be the one to call the destructor.

This scheme is not trivial to achieve and you should only shoot for it if there's major performance requirement from this hierarchy. If you decide to go down this road, you can also consider memory pools, that offer this kind of lifetime manipulation and have many of the related puzzles solved before hand.

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • For (1), could you explain the difference between the `func`s a bit more? When would I choose one over the other. For (2) I already do have the instances of Derived in static storage (the `BaseHolder` class) but I can't access it within the Derived constructor itself for reasons I mentioned. – Abob Nov 06 '21 at 21:29
  • @Arastais Version 1 keeps the state alive by creating a new shared pointer. Version 2 holds a weak pointer; the weak pointer has to covert to shared prior to being used and this conversion only succeeds if the state is already alive (but keeps the state alive once a shared ptr has been obtained) – Nikos Athanasiou Nov 07 '21 at 00:30
  • Makes sense, but what is `DerivedImpl`? and how would `_impl` hold a pointer to *the same/current instance*? Also, what is the advantage between one `func` over the other? Is it really just user preference? Also, could I still use a valid `this` pointer in either version of func so I don't have to do `impl->getName()` I can just do `getName()`? – Abob Nov 07 '21 at 01:04
  • `_impl` is what implements your interface i.e. it contains the definitions of the public members and the required state (like a [pointer to implementation](https://en.cppreference.com/w/cpp/language/pimpl#:~:text=%22Pointer%20to%20implementation%22%20or%20%22pImpl%22%20is%20a%20C%2B%2B,a%20separate%20class%2C%20accessed%20through%20an%20opaque%20pointer%3A). It does not hold a pointer to the instance, it's a different type from instance. It allows you to implement the Derived without worrying about expiration of internal state – Nikos Athanasiou Nov 07 '21 at 08:45
  • @Arastais The different types of `func` have different use cases, it's not a matter of pro/con. Do you want to be able to call a function only if the originator is alive? then use flavor 2. Do you want func to prolong the lifetime of the internal state? Use flavor 1 – Nikos Athanasiou Nov 07 '21 at 08:49
  • @Arastais think of it like this: Your problem with `enable_shared_from_this` was that you didn't guarantee shared state to begin with (a shared_ptr to Derived must exist before sharing from this), so there wasn't "memory" that the parties were sharing. By holding state/implementation in a shared pointer internally, you are always ready to share. The only particularity is that said state must also provide the same interface to be usable by func (like a pimpl idiom) – Nikos Athanasiou Nov 07 '21 at 08:55
  • You didn't fully explain how your using pointer to implementation in your answer, and its quite essential in order to understand what `DerivedImpl` is and how your answer works in general. Regardless, I understand how it works, and though I think its a suitable answer I don't think it will work very well in my case. If I just had one `Derived` class it's good but like I said I have many (`Derived1`, `Derived2`, etc.) and more may be added later. So, I don't want to have to make a separate public implementation class for every Derived class. – Abob Nov 08 '21 at 01:03
  • Also, I meant what are the different **effects** between the two `func`s? Meaning why or when would I want to "call a function only if the originator is alive" verses "prolong the lifetime of the internal state"? Don't they have the same effect? – Abob Nov 08 '21 at 01:10
  • 1
    @Arastais The difference in effects is literally in the description. If `func` has meaning when the object was supposed to die (as it seems to be your use case) then use flavor 1. If not, then use 2. For me, I've fallen into this pattern when updating local caches asyncrhonously and it's never useful to update the cache of a dead object; but since garbage collection is done from a different thread, I need my `func` to be able to avoid calling methods of a dead object; the twist is that `weak_ptr::lock` will cater for cases where destruction & update may overlap. – Nikos Athanasiou Nov 08 '21 at 18:37
  • @Arastais Regarding scalling (or avoid re-writing) there's always a way to abstract and avoid boilerplate code, but there's no point in going there if the solution seems unfit for your case. You can always use a ilbrary that provides safe pool allocation for your objects, maybe it's the easier solution. – Nikos Athanasiou Nov 08 '21 at 18:44
  • Well, i meant that other collaborators could add their own `Derived` classes and making also write a separate implementation class is undesirable for my specific application. The library on the other hand…do you happen to know of a library that provides save pool allocation like you said? Your solution is good and may work for other but like you said a library might be better for me, but i don't know of any specific ones that exist; I don’t think it’s something that’s easy to search for or describe – Abob Nov 09 '21 at 06:57
0

Here's an updated version based on your actual source code.

#include <future>
#include <functional>
#include <iostream>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>

//-------------------------------------------------------------------------------------------------
//Function class
struct FuncWrapper 
{
    //random data members
    std::string name;
    std::string description;

    std::function<void()> f;

    void operator()() const
    {
        std::cout << "Calling '" << name << "', description = '" << description << "'\n";
        f();
    }
};

//-------------------------------------------------------------------------------------------------

class FuncHolder 
{
public:
    size_t add(const FuncWrapper& wrapper) 
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        auto id = funcs.size();
        funcs.push_back(wrapper);
        return id;
    }

    const FuncWrapper& get(size_t index) 
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        return funcs.at(index);
    }

    ~FuncHolder() = default;

    // meyer's singleton, note this design is threadsafe by nature of C++11 or later
    // https://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton
    static FuncHolder& Instance()
    {
        static FuncHolder instance;
        return instance;
    }

private:
    FuncHolder() = default;
    std::vector<FuncWrapper> funcs;
    std::mutex m_mtx;
};

//-------------------------------------------------------------------------------------------------
// Data member base class

class Base 
{
public:
    explicit Base(const size_t& id) :
        storedIdx(id)
    {
    }

    //This is meant to be like a constexpr; its unique for each derived type
    inline virtual const std::string& getName() const = 0;

protected:
    //This will be important for a solution I tried
    const size_t storedIdx = 0;
};

//-------------------------------------------------------------------------------------------------

class Derived : 
    public Base 
{
public:
    explicit Derived(const size_t& id) : 
        Base(id),
        m_name{ "Derived" }
    {
        FuncHolder::Instance().add( { "add5", "adds 5", [this] { increment(5); } });
        FuncHolder::Instance().add( { "add1", "adds 1", [this] { increment(1); } });
    }

    inline const std::string& getName() const override 
    {
        return m_name;
    }

    void increment(int amount) 
    {
        m_value += amount;
    }

private:
    const std::string m_name;
    int m_value  = 0;
};

//-------------------------------------------------------------------------------------------------
//Class to hold the static instances of the different derived type

class BaseHolder 
{
public:
    // meyer's singleton
    static BaseHolder& Instance()
    {
        static BaseHolder instance;
        return instance;
    }
    
    template<typename type_t>
    size_t add()
    {
        static_assert(std::is_base_of_v<Base, type_t>,"type_t is not derived from base");
        auto id = m_objects.size();
        m_objects.push_back(std::make_unique<type_t>(id));
        return id;
    }

    template<typename type_t>
    const type_t* getInstance(size_t id)
    {
        static_assert(std::is_base_of_v<Base, type_t>);
        auto& base_ptr = m_objects.at(id);
        type_t* derived_ptr = dynamic_cast<type_t*>(base_ptr);
        return derived_ptr;
    }

private:
    std::vector<std::unique_ptr<Base>> m_objects;
};


int main() 
{
    // Create an instance of BaseHolder and  objects in itfirst this will ensure
    // that it will live longer then any threads
    auto derived_object_id = BaseHolder::Instance().add<Derived>(); 

    // no need to capture anything (you can also use std::thread here)
    auto future = std::async(std::launch::async, []
    {
       // making a call to Instance() of funcholder is threadsafe
       // the 0 is a bit of a magic number in your desing how would you determine it at runtime?
       auto func = FuncHolder::Instance().get(0);
       func();
    });

    // synchronize
    future.get();
    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • I understand what you're getting at, but you didn't really apply it to my example implementation. `my_class_ptr` wouldn't just be declared in the same scope, it would be stored in a static vector. Are you saying that I should use `shared_ptr` in my static vector instead of `unique_ptr`? Because that still doesn't change how the objects are constructed in the vector. – Abob Nov 08 '21 at 01:07
  • Also, I am assuming that the lambda created in the `std::async` constructor wouldn't be called until `future.get()` is called; is that correct? I want to point out that I don't really need a return value from my lambdas. – Abob Nov 08 '21 at 01:12
  • Example isn't a 1-1 solution for your problem, more like a this is how you can do it if you didn't design for life cycles of threads/objects beforehand. You could use unique_ptr's but only if you're sure about the life cycle of the static vector. It should be created before you start any threads, and stopped after all threads have ended. And then you would have to protect addition/deletion with mutexes. shared_ptr is kind of nice because it is threadsafe in its reference counting already and it still works when you don't have full control over lifecycle of the static vector. – Pepijn Kramer Nov 08 '21 at 05:15
  • No get won't invoke the lambda in this case, std::launch::async means start the lambda in a thread as soon as you can. Pretty much like starting a thread with a lambda. – Pepijn Kramer Nov 08 '21 at 05:16
  • "this is how you can do it if you didn't design for life cycles of threads/objects beforehand": well I did, so now I either need to redesign it or an answer that applies to the current design; in that case your answer doesn't apply to my question. Also "It should be created before you start any threads, and stopped after all threads have ended" is already true: I called `BaseHolder::init` before I constructed the lambda. – Abob Nov 08 '21 at 05:58
  • Oh it wasn't my intention to hurt your feelings. So my apologies if I did. I've updated the example using your code as a basis this time. If you have any questions happy to help. – Pepijn Kramer Nov 08 '21 at 11:14
  • Thanks for making it more applicable, though you could explain parts of it in detail more for other viewers. Also, "the 0 is a bit of a magic number in your desing how would you determine it at runtime?" that's what the id is for; In your example, you would use `derived_object_id` with `FuncHolder`'s get function. In my example, this id is actually stored in the derived object itself. Other than that, and how it's a singleton now, the `BaseHolder` class is unchanged. I also noticed that in your example you did not store the id for the derived object. Is there a reasoning behind this? – Abob Nov 08 '21 at 22:05
  • It would also be great if you could explain in your answer **how/why** you implementation is thread-safe compared to how I did it with all static members. And I don't just mean "Because I used singleton", I mean including the fact that you used locks, why using static members the way I did it didn't work, etc. I only `add` to `FuncHolder` in the constructor of a `Derived` class, and after that only `get` after the fact. – Abob Nov 08 '21 at 22:11
  • I tried your solution anyway and it did not seem to work. It made no difference and the problem is the same: the lambda could not acquire the this pointer properly. – Abob Nov 09 '21 at 06:52