Reputation: 7819
I'm pretty sure that the following question already has a good answer somewhere else, but it's difficult to find since I do not know the "name" of my problem.
I'm designing a class/object/"something" with the following properties:
So this sounds like a static template class:
template <int T>
class LookupTable{
public:
static void init(){
// create entries depending on T
}
private:
static vector<Entries> entries;
}
What I dislike is that I need to call init()
somewhere in my program. So the first question is: How can I make this class fully self-contained, with no need to be explicitly initialized somewhere?
Second part: What is the general design approach to implement such a class? I would be perfectly happy with a link to a good example.
A possible candidate is the singleton. But I have some doubts:
- The singleton considered bad design in many cases. Is it fine for a lookup table as described?
- Singleton is somewhat long notation, since I have to use LookupTable::getInstance()->getEntry(idx)
.
Upvotes: 8
Views: 1438
Reputation: 1211
Singleton is the pattern, but use the safer variant where this approach avoids static initialization order fiasco and thread race conditions and since you complained about the length - we can shorten it a bit further passing the indices through the get_entry function:
template <int T>
class LookupTable{
public:
static std::vector<Entry> make_entries(){ ...}
static const std::vector<Entry>& get_entries(){
static const std::vector<Entry> instances = make_entries();
return instances;
}
static const Entry& get_entry(size_t idx){
return get_entries()[idx];
}
};
Another approach that avoids all the evils of a singleton are not to use a singleton - just pass a regular old class in directly as just another parameter. I do this with many crc function implementations with relatively heavy tables... most stuff won't care and then you don't have to wig out on design patterns. And it's faster.
Upvotes: 5
Reputation: 13298
If you can compile with C++14, have you considered to use a variable template?
// Complicated initializer function that create entries depending on T
// could be specialized for T.
template <int T>
constexpr std::vector<Entries> init() { return {T, Entries{}}; }
// Class with several non-primitive members.
template <int T>
class LUT {
public:
constexpr LUT() : entries{init<T>()} {}
auto foo() const { return entries.size(); }
const void *bar() const { return entries.data(); }
const void *baz() const { return this; }
private:
std::vector<Entries> entries;
};
// Variable template parametrized by template parameters.
// It will be the same for the whole program.
template <int T>
LUT<T> LookupTable{};
void f15() { std::cout << LookupTable<15>.foo() << '\n'; }
void f5() { std::cout << LookupTable<5>.foo() << '\n'; }
int main()
{
// LookupTable<15> is the same here and in f15
std::cout << LookupTable<15>.foo() << ' '
<< LookupTable<15>.bar() << ' '
<< LookupTable<15>.baz() << '\n';
// LookupTable<5> is the same here and in f5
std::cout << LookupTable<5>.foo() << ' '
<< LookupTable<5>.bar() << ' '
<< LookupTable<5>.baz() << '\n';
return 0;
}
Did it achieve your requirements?
LUT
implementation.LookupTable
is initialized (before calling main
) it couldn't be changed*, be sure to mark all the LUT
functions as const
as well.LUT
implementation.init()
function as complicated as you want but considering that it will be called during static initialization.LookupTable<NUMBER>
instance is the same for the whole program for each NUMBER
provided.Hope it helps demo
* I don't know why template <int T>
const
LUT<T> LookupTable{};
fails, but anyway LUT
lacks of operator =
.
Upvotes: 1
Reputation: 27385
I'm designing a class/object/"something" with the following properties:
•It is sort of a lookup table.
class LookupTable
{
};
•It does not change after initialization.
client code:
const LookupTable lookup_table = ...;
^^^^^
•It has several non-primitive members.
class LookupTable
{
std::vector<Entry> entries;
^^^^^^^^^^^^^^^^^^^^^^^^^^^
};
•It has a complicated initializer function.
class LookupTable
{
std::vector<Entry> entries;
^^^^^^^^^^^^^^^^^^^^^^^^^^^
public:
explicit LookupTable(
std::vector<Entry> e
// if more members are required, receive them here,
// fully constructed
): entries{ std::move(e) } {}
};
LookupTable make_lookup_table()
{
std::vector<Entry> entries;
// perform complicated value initialization here
// and once everything is initialized, pass to new instance of
// LookupTable which is returned
return LookupTable{ std::move(entries) };
}
client code:
const auto lookup_table = make_lookup_table();
•It is the same for the whole program.
Use dependency injection in the code that uses it.
•It is parametrized by template parameters.
Simply add template parameters to the code above, as you need them.
Things to note:
there is nothing in the code to suggest a single instance will exist. That is part of the usage of the class (client code), not it's definition.
this is not a singleton. Singleton is (from many points of view) and antipattern.
you will probably need to define multiple instances of the class in the future (possibly for unit testing); there is nothing in here that prevents you from doing that.
the complex initialization part is centralized (and hidden) in a factory function. If initialization fails, no instance is constructed. If initialization changes, the public interface of the class will not change. If you need to add different initializations in different cases (debug vs. release, test vs. production, fast vs. safe runtime configurations) you will not need to delete or modify existing code - just add a new factory function.
Upvotes: 4
Reputation: 63144
Meyer's Singleton to the rescue !
template <class T>
struct LookupTable {
static LookupTable &get() {
static LookupTable lut;
return lut;
}
private:
LookupTable() {
// Your initialization
}
LookupTable(LookupTable const &) = delete;
LookupTable operator = (LookupTable const &) = delete;
};
Usage :
LookupTable<int>::get() // Will initialize on first call.
You can overload operators to simplify indexing, or even hide it in get()
.
Upvotes: 2
Reputation: 180945
If you want to make a class that is complete static that you never get an instance of and is only setup once then you should be able to use all static functions and have an Init()
function that doesn't return anything and determines if Init()
has already been called. This is just a tweak to the Singleton design.
So that you don't have to call Init()
somewhere in your code you can call Init()
as the first line of every function in the class. Since Init()
will do nothing if it has already been called it wont change anything. You can even make Init()
private if you want if you do that.
class StaticClass
{
public:
static void Init()
{
static bool created = false
if(!created)
{
// here we setup the class
created = true; // set to true so next time someone class Init() it is a do nothing operation.
}
}
//...
private:
StaticClass() {}
//...
};
Since there is no way to get an instance of a StaticClass
since the Init()
function is void you really don't need to worry about the copy constructor or the assignment operator.
Upvotes: 2
Reputation: 11716
You should be able to accomplish what you want by just having a static const
instance; you just need to give the class a default constructor (which would be equivalent to your init()
function). If you need different constructors based upon the type T
, then you can specialize LookupTable<T>
for those types.
With that said, there is one pitfall you should be aware of: the static initialization order fiasco. If you have other static
objects that refer to the LookupTable<T>
instance, then you can run into issues because the order that they are initialized in is unspecified.
Upvotes: 0