如何设计一个 class 初始化后常量并且在我的整个程序中只存在一次
How to design a class that is constant after initialization and exists only once in my whole program
我很确定以下问题在其他地方已经有了很好的答案,但很难找到,因为我不知道我的问题 "name"。
我正在设计具有以下属性的 class/object/"something":
- 这是一种查找 table。
- 初始化后不会改变
- 它有几个非原始成员。
- 它有一个复杂的初始化函数。
- 整个程序都是一样的
- 由模板参数参数化。
所以这听起来像一个静态模板 class:
template <int T>
class LookupTable{
public:
static void init(){
// create entries depending on T
}
private:
static vector<Entries> entries;
}
我不喜欢的是我需要在程序的某处调用init()
。所以第一个问题是:如何使这个 class 完全独立,不需要在某处显式初始化?
第二部分:实现这样一个 class 的一般设计方法是什么? 我会非常高兴 link 是一个很好的例子.
一个可能的候选者是单例。但我有一些疑问:
- 单身人士在很多情况下都被认为是糟糕的设计。可以按照描述进行查找 table 吗?
- Singleton 有点长,因为我必须使用 LookupTable::getInstance()->getEntry(idx)
.
你应该能够通过 static const
个实例来完成你想要的;你只需要给 class 一个默认的构造函数(这相当于你的 init()
函数)。如果您需要基于类型 T
的不同构造函数,那么您可以针对这些类型专门化 LookupTable<T>
。
话虽如此,您应该注意一个陷阱:the static initialization order fiasco。如果您有其他 static
对象引用 LookupTable<T>
实例,那么您可以 运行 进入问题,因为它们的初始化顺序未指定。
如果你想制作一个完全静态的 class,你永远不会得到它的实例并且只设置一次,那么你应该能够使用所有静态函数并有一个 Init()
函数不 return 任何东西并确定 Init()
是否已经被调用。这只是对 Singleton 设计的一个调整。
这样您就不必在代码中的某处调用 Init()
,您可以将 Init()
作为 class 中每个函数的第一行调用。因为 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() {}
//...
};
由于 Init()
函数无效,因此无法获得 StaticClass
的实例,因此您真的不需要担心复制构造函数或赋值运算符。
Singleton 是模式,但使用更安全的变体,这种方法避免了静态初始化顺序失败和线程竞争条件,并且由于您抱怨长度 - 我们可以进一步缩短它通过 [=15 传递索引=]函数:
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];
}
};
另一种避免单例所有弊端的方法是不使用单例 - 只需将常规的旧 class 作为另一个参数直接传递。我用许多带有相对繁重的表的 crc 函数实现来做到这一点......大多数东西都不会在意,然后你不必在设计模式上假发。而且速度更快。
Meyer 的 Singleton 来拯救!
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()
中。
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.
客户代码:
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) };
}
客户代码:
const auto lookup_table = make_lookup_table();
•It is the same for the whole program.
在使用它的代码中使用依赖注入。
•It is parametrized by template parameters.
只要在上面的代码中添加模板参数,就可以了。
注意事项:
代码中没有任何内容表明将存在单个实例。这是 class(客户端代码)用法的一部分,而不是它的定义。
这不是单例。单例是(从许多角度来看)和反模式。
您将来可能需要定义 class 的多个实例(可能用于单元测试);这里没有任何内容可以阻止您这样做。
复杂的初始化部分集中(并隐藏)在一个工厂函数中。如果初始化失败,则不构造任何实例。如果初始化改变,class的public接口不会改变。如果您需要在不同情况下添加不同的初始化(调试与发布、测试与生产、快速与安全运行时配置),您将不需要删除或修改现有代码 - 只需添加一个新的工厂函数。
如果可以用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;
}
达到你的要求了吗?
- 这是一种查找 table:我不知道,取决于
LUT
实现。
- 初始化后不改变:一旦
LookupTable
被初始化(在调用main
之前)就不能改变 *,请务必将所有 LUT
函数也标记为 const
。
- 它有几个非原始成员:我不知道,取决于
LUT
实现。
- 它有一个复杂的初始化函数:使
init()
函数尽可能复杂,但考虑到它会在静态初始化期间被调用。
- 整个程序都一样: 每个
LookupTable<NUMBER>
实例对于每个NUMBER
提供的整个程序都是一样的
- 它由模板参数参数化:据我所知。
希望对你有帮助demo
* 我不知道为什么 template <int T>
const
LUT<T> LookupTable{};
失败了,但是无论如何 LUT
缺少 operator =
.
我很确定以下问题在其他地方已经有了很好的答案,但很难找到,因为我不知道我的问题 "name"。
我正在设计具有以下属性的 class/object/"something":
- 这是一种查找 table。
- 初始化后不会改变
- 它有几个非原始成员。
- 它有一个复杂的初始化函数。
- 整个程序都是一样的
- 由模板参数参数化。
所以这听起来像一个静态模板 class:
template <int T>
class LookupTable{
public:
static void init(){
// create entries depending on T
}
private:
static vector<Entries> entries;
}
我不喜欢的是我需要在程序的某处调用init()
。所以第一个问题是:如何使这个 class 完全独立,不需要在某处显式初始化?
第二部分:实现这样一个 class 的一般设计方法是什么? 我会非常高兴 link 是一个很好的例子.
一个可能的候选者是单例。但我有一些疑问:
- 单身人士在很多情况下都被认为是糟糕的设计。可以按照描述进行查找 table 吗?
- Singleton 有点长,因为我必须使用 LookupTable::getInstance()->getEntry(idx)
.
你应该能够通过 static const
个实例来完成你想要的;你只需要给 class 一个默认的构造函数(这相当于你的 init()
函数)。如果您需要基于类型 T
的不同构造函数,那么您可以针对这些类型专门化 LookupTable<T>
。
话虽如此,您应该注意一个陷阱:the static initialization order fiasco。如果您有其他 static
对象引用 LookupTable<T>
实例,那么您可以 运行 进入问题,因为它们的初始化顺序未指定。
如果你想制作一个完全静态的 class,你永远不会得到它的实例并且只设置一次,那么你应该能够使用所有静态函数并有一个 Init()
函数不 return 任何东西并确定 Init()
是否已经被调用。这只是对 Singleton 设计的一个调整。
这样您就不必在代码中的某处调用 Init()
,您可以将 Init()
作为 class 中每个函数的第一行调用。因为 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() {}
//...
};
由于 Init()
函数无效,因此无法获得 StaticClass
的实例,因此您真的不需要担心复制构造函数或赋值运算符。
Singleton 是模式,但使用更安全的变体,这种方法避免了静态初始化顺序失败和线程竞争条件,并且由于您抱怨长度 - 我们可以进一步缩短它通过 [=15 传递索引=]函数:
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];
}
};
另一种避免单例所有弊端的方法是不使用单例 - 只需将常规的旧 class 作为另一个参数直接传递。我用许多带有相对繁重的表的 crc 函数实现来做到这一点......大多数东西都不会在意,然后你不必在设计模式上假发。而且速度更快。
Meyer 的 Singleton 来拯救!
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()
中。
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.
客户代码:
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) };
}
客户代码:
const auto lookup_table = make_lookup_table();
•It is the same for the whole program.
在使用它的代码中使用依赖注入。
•It is parametrized by template parameters.
只要在上面的代码中添加模板参数,就可以了。
注意事项:
代码中没有任何内容表明将存在单个实例。这是 class(客户端代码)用法的一部分,而不是它的定义。
这不是单例。单例是(从许多角度来看)和反模式。
您将来可能需要定义 class 的多个实例(可能用于单元测试);这里没有任何内容可以阻止您这样做。
复杂的初始化部分集中(并隐藏)在一个工厂函数中。如果初始化失败,则不构造任何实例。如果初始化改变,class的public接口不会改变。如果您需要在不同情况下添加不同的初始化(调试与发布、测试与生产、快速与安全运行时配置),您将不需要删除或修改现有代码 - 只需添加一个新的工厂函数。
如果可以用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;
}
达到你的要求了吗?
- 这是一种查找 table:我不知道,取决于
LUT
实现。 - 初始化后不改变:一旦
LookupTable
被初始化(在调用main
之前)就不能改变 *,请务必将所有LUT
函数也标记为const
。 - 它有几个非原始成员:我不知道,取决于
LUT
实现。 - 它有一个复杂的初始化函数:使
init()
函数尽可能复杂,但考虑到它会在静态初始化期间被调用。 - 整个程序都一样: 每个
LookupTable<NUMBER>
实例对于每个NUMBER
提供的整个程序都是一样的 - 它由模板参数参数化:据我所知。
希望对你有帮助demo
* 我不知道为什么 template <int T>
const
LUT<T> LookupTable{};
失败了,但是无论如何 LUT
缺少 operator =
.