C++ 如何设计一个初始化后保持不变且在整个程序中只存在一次的类

C++ 如何设计一个初始化后保持不变且在整个程序中只存在一次的类,c++,design-patterns,static,C++,Design Patterns,Static,我很确定下面的问题在其他地方已经有了很好的答案,但是很难找到,因为我不知道问题的“名称” 我正在设计一个具有以下属性的类/对象/“某物”: 这是一种查找表 它在初始化后不会更改 它有几个非原始成员 它有一个复杂的初始化函数 整个计划也是如此 它由模板参数进行参数化 这听起来像是一个静态模板类: template <int T> class LookupTable{ public: static void init(){ // create entries

我很确定下面的问题在其他地方已经有了很好的答案,但是很难找到,因为我不知道问题的“名称”

我正在设计一个具有以下属性的类/对象/“某物”:

  • 这是一种查找表
  • 它在初始化后不会更改
  • 它有几个非原始成员
  • 它有一个复杂的初始化函数
  • 整个计划也是如此
  • 它由模板参数进行参数化
这听起来像是一个静态模板类:

template <int T>
class LookupTable{

  public:
    static void init(){
      // create entries depending on T
    }

  private:
    static vector<Entries> entries;

}
模板
类可查找{
公众:
静态void init(){
//根据T创建条目
}
私人:
静态向量项;
}
我不喜欢的是我需要在程序中的某个地方调用
init()
。因此,第一个问题是:如何使这个类完全自包含,而不需要在某个地方显式初始化?

第二部分:实现这样一个类的一般设计方法是什么?我非常乐意链接到一个好的示例

一个可能的候选人是单身汉。但我有一些疑问: -单身汉在很多情况下认为设计不好。对于如上所述的查找表是否合适?
-Singleton有点长,因为我必须使用
LookupTable::getInstance()->getEntry(idx)
您应该能够通过拥有一个
静态常量
实例来完成您想要的任务;您只需要给这个类一个默认构造函数(这相当于您的
init()
函数)。如果您需要基于类型
T
的不同构造函数,那么您可以为这些类型专门化
LookupTable


话虽如此,有一个陷阱你应该意识到:。如果有其他引用可查找实例的
静态
对象,然后,您可能会遇到问题,因为它们的初始化顺序是未指定的。

如果您想创建一个完全静态的类,而该类从未获得实例,并且只设置了一次,那么您应该能够使用所有静态函数并具有
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()
函数是空的,所以您确实不需要担心复制构造函数或赋值运算符。

单例是模式,但是使用更安全的变体,这种方法可以避免静态初始化顺序失败和线程争用情况,并且由于您抱怨长度,我们可以通过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];
    }
};
模板
类可查找{
公众:
静态std::vector make_entries(){…}
静态常量std::vector&get_entries(){
static const std::vector instances=make_entries();
返回实例;
}
静态常量条目和获取条目(大小idx){
返回get_条目()[idx];
}
};

另一种避免单例的所有缺点的方法是不使用单例,只需直接将一个常规的旧类作为另一个参数传入即可。我用很多crc函数实现来做这件事,它们都有相对较重的表。。。大多数东西都不会在意,这样你就不必在设计模式上发疯了。而且速度更快。

梅耶的独生子女来营救

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&get(){
静态可查找lut;
返回lut;
}
私人:
可查找的{
//您的初始化
}
LookupTable(LookupTable const&)=删除;
LookupTable运算符=(LookupTable常量&)=删除;
};
用法:

LookupTable<int>::get() // Will initialize on first call.
LookupTable::get()//将在第一次调用时初始化。
您可以重载运算符以简化索引,甚至可以将其隐藏在
get()

我正在设计一个具有以下属性的类/对象/“某物”:

•它是一种查找表

•初始化后不会发生变化

客户端代码:

const LookupTable lookup_table = ...;
^^^^^
const auto lookup_table = make_lookup_table();
•它有几个非原始成员

•整个计划都是一样的

在使用依赖项注入的代码中使用依赖项注入

•通过模板参数对其进行参数化

只需根据需要向上面的代码中添加模板参数即可

注意事项:

  • 代码中没有任何内容表明将存在单个实例。这是类(客户机代码)用法的一部分,而不是它的定义

  • 这不是单身汉。从许多角度来看,单例是反模式的

  • 将来可能需要定义该类的多个实例(可能用于单元测试);这里没有任何东西可以阻止你这么做

  • 复杂的初始化部分集中(隐藏)在工厂函数中。如果初始化失败,则不会构造任何实例。如果初始化更改,则类的公共接口不会更改。如果您需要在不同的情况下添加不同的初始化(调试与发布、测试与生产、快速与安全运行时配置),则无需删除或修改现有代码-只需添加新的factory函数


如果您可以使用C++14进行编译,请
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();
// 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;
}