Consider the behaviour of a static member in templates
In a templated class, a static member is instantiated for each instantiated fully deduced template, i.e. they all share the single instance of data.
Consequently, declaring the vector static is enough.
BUT be aware
...that even though the data is shared, all LoadData()-calls would reload or append new data!
See the extensive example below!
#include <iostream>
#include <vector>
#include <atomic>
#include <mutex>
template <typename T>
// Simple creator class providing us data
class Creator {
public:
static T Create() {
return T();
}
};
template <>
// Simple creator class providing us data for INTEGER, just counting up from 0 on each call.
class Creator<int> {
static int mCounter;
public:
static int Create() {
return mCounter++;
}
};
// Don't forget to initialize.
int Creator<int>::mCounter = 0;
template <typename T>
// Loader class encapsulating thread-synchronization, "already-loaded"-handling and effective data loading mechanisms...
// Here: Using the Creator<T>
class Loader {
public:
Loader()
: mLoaded(false),
mData() {
}
bool LoadData() {
// Deactivate check, to allow confirming duplicate addition of data from two equally-typed containers! EXAMPLE ONLY
// Uncomment, to have the "load only once" functionality.
// if(mLoaded.load())
// return true;
std::lock_guard<std::mutex> guard(mLoadMutex);
bool loaded = LoadDataImpl();
if(loaded)
mLoaded.store(loaded);
return loaded;
}
const std::vector<T>& ConstData() const { return mData; }
private:
bool LoadDataImpl() {
// Actual data loading. Return true on success. False otherwise.
try {
// Code...
for(int i=0; i<10; ++i) {
mData.push_back(Creator<T>::Create());
}
return true;
} catch(...) { // Actually don't use the ... but specific types.
return false;
}
}
std::mutex mLoadMutex;
std::atomic<bool> mLoaded;
std::vector<T> mData;
};
template <typename T>
// The actual container, using the STATIC loader.
// The Loader<T>-static instance is shared among all Container<T>'s!
class Container {
static Loader<T> mLoader;
public:
bool LoadData() { return mLoader.LoadData(); }
const std::vector<T>& Data() const { return mLoader.ConstData(); }
};
// Don't forget to initialize...
template <typename T>
Loader<T> Container<T>::mLoader = {};
// Example struct
struct Whatever {
int whateverValue;
};
int main() {
Container<int> intContainer;
Container<int> intContainer2;
Container<bool> boolContainer;
Container<Whatever> whateverContainer;
// Load data 0..10 into buffer
if(!intContainer.LoadData()) {
std::cout << "Cannot load data of container 1.\n";
} else {
std::cout << "Got C1 data.\n";
}
// Load data 11..19 into buffer, IF we have the above check for mLoaded commented.
// Otherwise, nothing will happen here!
if(!intContainer2.LoadData()) {
std::cout << "Cannot load data of container 2.\n";
} else {
std::cout << "Got C2 data.\n";
}
//
// If we commented the mLoaded-precondition, we will get a print of 0..19 in the console.
// Otherwise 0..9
//
for(const int& v : intContainer2.Data()) {
std::cout << v << "\n";
}
std::cout << std::endl;
}
I have created a small Demo, which:
- Declares a Container
- Using a Loader (which is shared among all COntainer's) as it is static.
- The Loader uses a Creator as a dummy-databackend, counting 0 up on each call for int-types
THe important part here is, that the Loader provides locking for multithreaded access and an atomic "mLoaded", which checks if data was already loaded beforehand.
Since the additional dataloading logic would make the container itself clumsy, I encapsulated it in a Loader and have the beautiful one-liner static Loader mLoader.
Just read through the code and the comments in it and it should become appearent!