如何设计一个 class 初始化后常量并且在我的整个程序中只存在一次

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

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

我正在设计具有以下属性的 class/object/"something":

所以这听起来像一个静态模板 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 =.