The associated data pattern allows a class C that is used by multiple other classes outside its own module (which don't control or know the lifetime of C) to store data that is logically “per instance of C”. This document will call those other classes the “consumer classes” of C.
Imagine that you have a class Pokemon, which is used by two other classes, PokemonGroomer and PokemonNurse. A PokemonGroomer wishes to store the last time a given Pokemon got a bath, and a PokemonNurse wishes to store the last time a given Pokemon got a checkup.
You might implement that this way:
class Pokemon { ... Time last_bath_; Time last_checkup_; ... }; bool PokemonNurse::NeedsCheckup(Pokemon* p) { return Time::Now() - p->last_checkup_ > kCheckupInterval; }
but then Pokemon ends up containing (and being responsible for enforcing invariants on) data that is only for the use of other objects, which might live in entirely separate modules.
You might instead do this:
class PokemonNurse { ... map<Pokemon*, Time> last_checkup_; ... }; bool PokemonNurse::NeedsCheckup(Pokemon* p) { if (!last_checkup_.contains(p)) last_checkup_[p] = 0; return Time::Now() - last_checkup_[p] > kCheckupInterval; }
but another problem appears: PokemonNurse has to know when Pokemon are released into the wild so it can clean up the map, which adds extra bookkeeping.
The associated data pattern would look like this:
class Pokemon { ... struct Data { virtual ~Data(); }; map<Key, unique_ptr<Data>> user_data_; ... }; class PokemonNurse::PokemonData : public Pokemon::Data { Time last_checkup_; }; bool PokemonNurse::NeedsCheckup(Pokemon* p) { if (!p->user_data_[PokemonNurse::kDataKey]) p->user_data_[PokemonNurse::kDataKey] = make_unique<PokemonData>(); return Time::Now() - p->user_data_[PokemonNurse::kDataKey].last_checkup_ > kCheckupInterval; }
This way, Pokemon manages the lifetime of the per-Pokemon data, but PokemonNurse manages the invariants of the data, and only PokemonNurse is aware of the per-Pokemon data belonging to PokemonNurse.
The two most commonly-used instances of this pattern in Chromium are SupportsUserData (especially WebContentsUserData and WebStateUserData) and KeyedService (usually via BrowserContextKeyedServiceFactory and BrowserStateKeyedServiceFactory).
SupportsUserData is a mixin-type class that allows consumer classes to stash data on the class with the mixin. It exposes a very small interface SupportsUserData::Data
which stored data items implement (in fact, any class with a virtual destructor already implements it), and adds a handful of new methods to the class with the mixin:
Data* GetUserData(const void* key); void SetUserData(const void* key, std::unique_ptr<Data> data); void RemoveUserData(const void* key);
For example, in //net, URLRequest inherits from SupportsUserData, so if you were running a RequestNicenessService that wanted to annotate URLRequests with a niceness value at one point and use that value later, you might do:
class RequestNicenessService { static char kRequestDataKey; void SetRequestNiceness(net::URLRequest* request, int niceness); int GetRequestNiceness(net::URLRequest* request); }; struct NicenessData : public SupportsUserData::Data { int niceness; }; void RequestNicenessService::SetRequestNiceness(...) { request->SetUserData(&kRequestDataKey, make_unique<NicenessData>(niceness)); } int RequestNicenessService::GetRequestNiceness(...) { SupportsUserData::Data* data = request->GetUserData(&kRequestDataKey); if (data) return static_cast<NicenessData*>(data)->niceness; return -1; }
Or, if there might be multiple RequestNicenessService instances, you could use the address of the RequestNicenessService instance itself as the key for the UserData on the URLRequest. That's actually a more common pattern, but for a singleton instance using the address of a static is also fine.
For the specific very common case of wanting to attach a SupportsUserData::Data to a WebContents, the helper class WebContentsUserData exists. This templated class handles casting to and from your concrete type for you. It is commonly used with the related TabHelper pattern.
KeyedService is an abstract interface that implements this pattern, but with an additional notion of dependencies between different pieces of associated data. It is usually concretely used via BrowserContextKeyedServiceFactory, by subclassing BrowserContextKeyedServiceFactory and overriding the BuildServiceInstanceFor
method. Since a Profile is a BrowserContext, this provides a way to store data associated with a Profile. For example, if you were creating a UserNicenessController, you might do:
class UserNicenessData : public KeyedService { public: static UserNicenessData* FromProfile(Profile* profile); int niceness; // Maybe also: // void Shutdown() override; // if you need to participate in KeyedService's two-phase destruction // protocol. }; class UserNicenessController : public BrowserContextKeyedServiceFactory { ... }; // static UserNicenessData* UserNicenessData::FromProfile(Profile* profile) { return static_cast<UserNicenessData*>( UserNicenessController::GetInstance()-> GetServiceForBrowserContext(profile, true)); } // in code somewhere: UserNicenessData::FromProfile(profile)->niceness++;
Any code that needs the UserNicenessData for a given Profile can call UserNicenessData::FromProfile
, and KeyedService and BrowserContextKeyedServiceFactory will handle creation and lifetime as needed, as well as handling any dependencies UserNicenessData might declare on other services.
Note that the naming of KeyedService implies that it is intended for services, as does the presence of a dependency mechanism, but the same approach can be used to store plain data, as in this example.
Another note: Since Profile
already SupportsUserData, if you simply want to store data on the profile, it is easier to do it that way rather than use KeyedService.
A third note: on iOS, use BrowserStateKeyedServiceFactory instead, which attaches a KeyedService to a BrowserState.
A fourth note: on Android, C++ keyed services often have a corresponding Java object. The C++ part should own the Java one, as detailed in JavaCppOwnership.