我很确定以下问题在其他地方已经有了一个很好的答案,但由于我不知道"名称"它很难找到。我的问题。
我正在设计一个类/对象/"某些东西"具有以下属性:
所以这听起来像一个静态模板类:
template <int T>
class LookupTable{
public:
static void init(){
// create entries depending on T
}
private:
static vector<Entries> entries;
}
我不喜欢的是我需要在我的程序中的某处调用init()
。所以第一个问题是:如何让这个类完全自包含,不需要在某处明确初始化?
第二部分:实现这样一个类的一般设计方法是什么?我很高兴看到一个很好的例子的链接。
可能的候选人是单身人士。但我有些疑惑:
- 在许多情况下,单身人士认为设计不好。如上所述,查找表是否可以?
- Singleton有点长,因为我必须使用LookupTable::getInstance()->getEntry(idx)
。
答案 0 :(得分:5)
Singleton是模式,但是使用更安全的变体,这种方法避免了静态初始化顺序惨败和线程竞争条件,并且因为你抱怨长度 - 我们可以通过get_entry函数进一步缩短它的索引:
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];
}
};
避免单身人士的所有邪恶的另一种方法是不使用单身人士 - 只是直接将常规旧类作为另一个参数传递。我使用相对繁重的表格进行许多crc函数实现...大多数东西都不会关心,然后你就不必在设计模式上掏空了。而且速度更快。
答案 1 :(得分:4)
我正在设计一个具有以下属性的类/对象/“东西”:
•它是一种查找表。
class LookupTable
{
};
•初始化后它不会改变。
客户代码:
const LookupTable lookup_table = ...;
^^^^^
•它有几个非原始成员。
class LookupTable
{
std::vector<Entry> entries;
^^^^^^^^^^^^^^^^^^^^^^^^^^^
};
•它具有复杂的初始化函数。
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) };
}
客户代码:
const auto lookup_table = make_lookup_table();
•整个计划都是一样的。
在使用它的代码中使用依赖注入。
•它由模板参数参数化。
只需将模板参数添加到上面的代码中,就像您需要的那样。
注意事项:
代码中没有任何内容可以建议单个实例存在。这是使用类(客户端代码)的一部分,而不是它的定义。
这不是单身人士。单身人士(从许多角度来看)和反模式。
您可能需要在将来定义该类的多个实例(可能用于单元测试);这里没有任何东西阻止你这样做。
复杂初始化部分在工厂功能中集中(和隐藏)。如果初始化失败,则不构造任何实例。如果初始化更改,则类的公共接口不会更改。如果您需要在不同的情况下添加不同的初始化(调试与发布,测试与生产,快速与安全运行时配置),您将不需要删除或修改现有代码 - 只需添加新的工厂函数。
答案 2 :(得分:2)
如果你想创建一个完全静态的类,你永远不会得到一个实例,并且只设置一次,那么你应该能够使用所有静态函数并拥有一个不返回的Init()
函数任何事情并确定是否已调用Init()
。这只是对Singleton设计的一个调整。
因此,您无需在代码中的某处调用Init()
,您可以将Init()
作为类中每个函数的第一行。由于Init()
已经被调用,它将不会做任何事情,因为它不会改变任何东西。如果您愿意,甚至可以将Init()
设为私人。
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() {}
//...
};
由于StaticClass
函数无效,因此无法获取Init()
的实例,因此您无需担心复制构造函数或赋值运算符。
答案 3 :(得分:2)
迈耶的辛格尔顿救援!
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;
};
用法:
LookupTable<int>::get() // Will initialize on first call.
您可以重载运算符以简化索引,甚至可以在get()
中隐藏它。
答案 4 :(得分:1)
如果你可以用C ++ 14编译,你考虑过使用变量模板吗?
// 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;
}
它符合您的要求吗?
LUT
实施。LookupTable
后(在调用main
之前)它无法更改 * ,请务必将所有LUT
功能标记为const
。LUT
实施。init()
函数尽可能复杂,但考虑到它将在静态初始化期间调用。LookupTable<NUMBER>
实例对于提供的每个NUMBER
的整个程序都是相同的。希望它能帮助 demo
* 我不知道为什么template <int T>
const
LUT<T> LookupTable{};
失败,但无论如何LUT
缺乏operator =
。
答案 5 :(得分:0)
你应该能够通过static const
个实例来完成你想要的任务;你只需要给类一个默认的构造函数(这相当于你的init()
函数)。如果您需要基于类型T
的不同构造函数,那么您可以为这些类型专门化LookupTable<T>
。
话虽如此,你应该注意一个陷阱:the static initialization order fiasco。如果您有其他引用static
实例的LookupTable<T>
个对象,那么您可能会遇到问题,因为它们的初始化顺序是未指定的。