jeudi 26 juillet 2018

Thread-safe Factory pattern without control over Derived classes

Say I have a class 'Car' with a complicated tree of subclasess. Each class has a unique id. I have a management class that keeps track of pointers to all cars and can look them up based on the id.

class Car {
public:
    using CarId = size_t;
    Car* getCar(CarId id) const { return sMap[id]; }
    Car(){
        sMutex.lock();
        mId = ++sLastId;
        sMap.insert(pair(mId,this));
        sMutex.unlock();
    }
private:
    static map<CarId,Car*> sMap;
    static CarId sLastId;
    static mutex sMutex;
    CarId mId;
}

I want to refactor this so that the Car::getCar returns weak_ptr as returning raw pointers causes a hell lot of problems.

However, to add weak pointer to itself into sMap from a constructor in a naive way one needs to create a shared pointer, which immediately destroys the object as it gets out of scope:

sMutex.lock();
mId = ++sLastId;
sMap.insert(
    pair(mId,
         weak_ptr<Car>(shared_ptr(this))
    )
);
sMutex.unlock();

Shit, I guess I'll need to use some factory:

class CarFactory {
public:
    shared_ptr<Car> createCar(){
        sMutex.lock();
        mId = ++sLastId;
        auto res = shared_ptr<Car>(this);
        sMap.insert(
            pair(mId,
                 weak_ptr<Car>(res)
            )
        );
        sMutex.unlock();
        return result;
    }

private:
    map<CarId,Car*> sMap;
    CarId sLastId;
    mutex sMutex;
}

Nice, but there is a lot of derived classes, many times in modules that I can not include from here, so I can not have a factory method for every possible Car derived type defined here, it would be quite impractical. However I need every car to be tracked, I can not allow any way to create derived cars that does not go through the factory method.

The best solution I have come up with so far:

class CarFactory {
...
template<class DT, class Tp>
shared_ptr<DT> create(Tp params...)
{
    sMutex.lock();
    mId = ++sLastId;
    auto res = shared_ptr<Car>(new DT(forward(params)...) );
    sMap.insert(
        pair(mId,
             weak_ptr<Car>(res)
        )
    );
    sMutex.unlock();
    return result;
}
...
}

Which allows to privatize derived classes constructor:

class VolksWagenGolf : public VolksWagen {
private:
    VolksWagen(bool enableEmissionCheat);
}

Hence I can create it only as follows: shared_ptr myCar = CarFactory::create(true);

Right, but I still don't like it because:

1) It is a template, for various reasons it would be better to do it without a template. 2) Any time a colleague unaware of this mechanism can come and declare a new Car type with a public constructor as he was used to do always. But now when he uses that public constructor, his cars are not going to be tracked.

So if you have any ideas how to implement such a pattern where every derived class has to be created through the centralized bookkeeping mechanism while using weak pointers at the same time for tracking, please let me know.

Thanks

Aucun commentaire:

Enregistrer un commentaire