如何设计初始化后不变的类,并且在我的整个程序中仅存在一次

How to design a class that is constant after initialization and exists only once in my whole program

本文关键字:程序 我的 存在 一次 初始化      更新时间:2023-10-16

我很确定以下问题已经在其他地方有一个很好的答案,但是很难找到问题,因为我不知道我的问题的"名称"。

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

  • 这是一个查找表。
  • 初始化后不会改变。
  • 它有几个非主要成员。
  • 它具有复杂的初始化功能。
  • 整个程序都是一样的。
  • 它是由模板参数进行参数化的。

所以这听起来像静态模板类:

template <int T>
class LookupTable{
  public:
    static void init(){
      // create entries depending on T
    }
  private:
    static vector<Entries> entries;
}

我不喜欢的是我需要在程序中的某个地方调用init()。因此,第一个问题是:如何使此类完全独立,而无需在某个地方明确初始化?

第二部分:实现此类类的一般设计方法是什么?我对一个好示例的链接感到非常满意。

可能的候选人是单身人士。但是我有一些疑问: - 单身人士在许多情况下被认为是不良设计。如上所述的查找表是否可以? - 单例有点长,因为我必须使用 LookupTable::getInstance()->getEntry(idx)

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函数实现了相对较重的表...大多数东西都不在乎,然后您不必在设计模式上摇摆。而且它更快。

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

•这是一个查找表。

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();

•整个程序都是相同的。

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

•通过模板参数进行了参数。

只需在需要时将模板参数添加到上面的代码。

要注意的事情:

  • 代码中没有任何建议将存在一个实例。这是类使用(客户端代码)的一部分,而不是定义。

  • 这不是单身人士。Singleton是(从许多角度来看)和对抗。

  • 您可能需要在将来定义类的多个实例(可能用于单位测试);这里没有什么可以阻止您这样做。

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

如果要完成一个完全静态的类,该类是您永远不会得到实例的,并且仅设置了一次,那么您应该能够使用全部静态功能并具有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()函数是无效的,因此您实际上不必担心复制构造函数或分配运算符。

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()中。

如果您可以使用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>的每个 CC_19实例都相同。
  • 它是通过模板参数进行参数化的:afaik是。

希望它能帮助 demo


*我不知道为什么template <int T> const LUT<T> LookupTable{};失败,但是无论如何LUT缺少operator =。">

您应该能够通过仅使用static const实例来完成想要的工作;您只需要给班级一个默认的构造函数(这等同于您的init()函数)。如果您需要基于T类型的不同构造函数,则可以专门为这些类型的LookupTable<T>

话虽如此,您应该知道一个陷阱:静态初始化顺序惨败。如果您有其他static对象,请参考LookupTable<T>实例,则可以遇到问题,因为未指定它们的初始化顺序。