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

时间:2015-05-06 11:47:09

标签: c++ design-patterns static

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

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

  • 这是一种查找表。
  • 初始化后不会改变。
  • 它有几个非原始成员。
  • 它具有复杂的初始化函数。
  • 整个计划都是一样的。
  • 通过模板参数进行参数化。

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

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)

6 个答案:

答案 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的整个程序都是相同的。
  • 它由模板参数进行参数化:AFAIK是。

希望它能帮助 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>个对象,那么您可能会遇到问题,因为它们的初始化顺序是未指定的。