vendredi 22 juin 2018

std::call_once with multiple expensive functions

Is it possible to use std::call_once in a scenario where the number of expensive functions varies?

Imagine a factory computing expensive things according to a template parameter ResultType, e.g. edge adjacency or averaged vertex normals for a triangle mesh:

std::unordered_map<std::string, std::unique_ptr<AbstractResult>> mResults;
std::mutex mMutex;

template<typename ResultType, typename ...Params>
const ResultType& getResult(Params&&... params)
{
    std::lock_guard<std::mutex> lock(mMutex);

    const std::string& hash = ResultType::computeHash(std::forward<Params>(params)...);
    auto it = mResults.find(hash);

    if (it == mResults.end())
    {
        // Expensive call
        mResults[hash] = std::make_unique<ResultType>(std::forward<Params>(params)...);

        it = mResults.find(hash);
    }

    return *static_cast<ResultType*>(it->second.get());
}

The lock is only necessary as long as the result has not been created. To avoid unnecessary locking for reading only, double-checked locking can be used. There are various examples how to do this correcly in C++11, that's not a problem.

Yet, my understanding is that std::call_once offers an elegant solution for this and should be used to avoid manual double-checked locking. I am aware how to use std::call_once with a single std::once_flag variable. However, how would I use std::call_once in my example? For each different ResultType, I need an std::once_flag. How do I store the arbitrary number of once_flags? I cannot store them in a second unordered_map addressed with the results' hashes, as once_flags are neither copyable nor movable. Am I missing something obvious here?

All possible ResultTypes (and therefore the number of std::once_flags), which are in the order of 30, are known at compile time. However, there is no list of all possible ResultTypes, this is only known from all calls to getResult<ResultType>. If I can solve this with template magic, I'm thankful for any input, because I don't know how. If there is any MSVC-specific solution, I'm happy to take that as well.

Aucun commentaire:

Enregistrer un commentaire