【问题标题】:Class with private constructor and static array of itself具有私有构造函数和自身静态数组的类
【发布时间】:2014-12-30 00:37:26
【问题描述】:

对不起,如果标题令人困惑,我找不到一个简单的方法来用一个简单的句子来写它。无论如何,我面临的问题:

 // header:
class SomeThing
{
 private:
   SomeThing() {} // <- so users of this class can't come up
                  //    with non-initialized instances, but
                  //    but the implementation can.

   int some_data; // <- a few bytes of memory, the default
                  //    constructor SomeThing() doesn't initialize it
 public:
   SomeThing(blablabla ctor arguments);

   static SomeThing getThatThing(blablabla arguments);

   static void generateLookupTables();
 private:

   // declarations of lookup tables
   static std::array<SomeThing, 64> lookup_table_0;
   static SomeThing lookup_table_1[64];
};

getThatThing 函数旨在从查找表中返回一个实例。

 // in the implementation file - definitions of lookup tables

 std::array<SomeThing, 64> SomeThing::lookup_table_0; // error

 SomeThing Something::lookup_table_1[64]; // <- works fine

我不能使用Somethingstd::array,除非我在课堂上添加一个公共ctor SomeThing()。它适用于旧式数组,我可以定义数组,并将其填充到 SomeThing::generateLookupTables() 函数中。显然std::array&lt;SomeThing, 64&gt; 类型没有构造函数。关于如何使其发挥作用的任何想法,或者可能为这个概念提供更好的结构?

============= 编辑 =======

friend std::array&lt;SomeThing, 64&gt; 方法似乎是个好主意,但是:

它也将用于其他地方的数组中。我想保证这个类总是对外部用户保持某些不变量。使用这个友好的数组,用户可能会意外创建一个未初始化的 SomeThing 数组。

此外,查找表是使用相当复杂的过程生成的,不能按内联方式完成,如std::array&lt;SomeThing, 64&gt; SomeThing::lookup_table_0(some value)

【问题讨论】:

  • 如果您的课程是可移动的,请考虑使用std::vector。如果它不是可移动的,我认为std::deque 仍然可以工作,当然前提是你就位。
  • 我想知道这个类定义是否格式错误,标准容器应该只用完整类型实例化。 (而且这个问题只是由于类包含来自自身模板的静态成员)
  • Matt:该标准容器尚未在标头中实例化,而仅在实现中。
  • 这个问题很有趣,因为它是 C 样式数组不能简单地替换为 std::array
  • 我想我还有另一个半解决方案:如果 C++ 在这方面的工作方式与 C 相同,则全局变量,例如Something::lookup_table_0(它不在任何函数的堆栈中,所以是全局的) 被初始化为清零内存。因此,如果我创建一个仅将 some_data 设置为零的私有构造函数,并使用它对 std::array 进行值初始化,这本质上是相同的。但它仍然是一个 hack。

标签: c++ c++11 initialization static-initialization stdarray


【解决方案1】:

std::array&lt;SomeThing, 64&gt; 类在尝试定义实例时显然无法访问private 默认构造函数。您可以通过添加来授予它必要的访问权限

friend class std::array<SomeThing, 64>;

SomeThing的定义。

【讨论】:

    【解决方案2】:

    解决办法:

    std::array<SomeThing, 64> SomeThing::lookup_table_0 {{ }};
    

    注意: 正如here 所解释的,{{}} 需要在 gcc 中对 std::array 进行值初始化而不会出现警告。 = {}{} 是正确的,但 gcc 还是会发出警告。

    解决方案的关键是必须存在某种形式的初始化程序。


    首先检查术语:所有对象都在 C++ 中初始化。这个有三种形式,defaultvaluezero。没有“未初始化”的对象;没有显式初始化的对象被称为 default-initialized。在某些情况下,这意味着对象的成员变量可能是不确定的(“垃圾”)。

    no-initializer 版本有什么问题?首先,std::array&lt;SomeThing, 64&gt; 的构造函数被定义为已删除,因为声明 std::array&lt;SomeThing, 64&gt; x; 格式不正确(当然,由于缺少可访问的 SomeThing 的默认构造函数)。 p>

    这意味着任何尝试使用std::array&lt;SomeThing, 64&gt; 的默认构造函数的代码都是格式错误的。定义:

    std::array<SomeThing, 64> SomeThing::lookup_table_0;
    

    确实尝试使用默认构造函数,因此它的格式不正确。然而,一旦你开始引入初始化器,std::array 的默认构造函数就不再起作用了;因为std::array 是一个聚合,然后聚合初始化 发生,它绕过隐式生成的构造函数。 (如果有任何用户声明的构造函数,那么它将不再是一个聚合)。

    由于 [dcl.init]/13 (n3936),带有初始化程序的版本可以工作:

    静态成员的初始化器在成员类的范围内

    列表初始化的定义在这里将{ }转换为{ SomeThing() }

    【讨论】:

    • 对我来说,这会导致:“错误:缺少成员 'std::array::_M_elems' [-Werror=missing-field-initializers] 的初始化程序”
    • 这与其他解决方案 lookup_table_0( SomeThing() } 基本相同。非初始化和值初始化是有区别的。无论如何,现在我想我会继续使用旧式数组。
    • 经过一番思考,我想我可以接受这个作为解决方案,期待编译器优化器认识到无论如何都没有工作要做。顺便说一句,我刚刚尝试了这个构造函数:SomeThing() { std::cout &lt;&lt; "ctor here\n"; } 它使用您的示例lookup_table_0 {{ }} 打印“ctor here\n” 64 次,但也使用 C 样式数组打印 64 次。我最初是在寻找“一块大小为 64*sizeof(SomeThing) 的内存”的概念,而没有运行 64 次构造函数,但我猜无论如何,任何体面的编译器的结果都是一样的。
    • 好的。您可以在准备好时分配一块内存并在其上使用新的放置,但这会更加混乱。如果您的默认 ctor 实际上什么都不做,无论如何它都会被优化。
    【解决方案3】:

    由于你的构造函数是私有的,std::array 不能使用它。

    您可以在SomeThing 中添加friend class std::array&lt;SomeThing, 64&gt;; 以授予对构造函数的访问权限。

    另一种方法是使用可用的公共构造函数来初始化数组的元素:

    std::array<SomeThing, 64> SomeThing::lookup_table_0{
        SomeThing(blablabla_ctor_arguments), ..
    };
    

    编辑:

    如果您有可用的移动或复制构造函数,您甚至可以这样做:

    std::array<SomeThing, 64> SomeThing::lookup_table_0{ SomeThing() };
    

    让你的整个数组默认初始化。

    【讨论】:

    • 谢谢,但正如我所见,这会暴露私有构造函数。在问题中查看我的编辑。
    • @BuellaGábor: 看来我找到了神奇的解决方案:)
    • 哇,太聪明了。我认为,它实际上创建了一个实例,使其未使用垃圾内存进行初始化,并制作了该垃圾内存的另外 63 个副本。这有点愚蠢,但从技术上讲,这是“有效的”。
    • @BuellaGábor SomeThing() 调用默认构造函数,没有“非用垃圾内存初始化”
    • @MattMcNabb :看看问题中的构造函数:它什么都不做。同时,该类实际上确实具有成员变量(问题中未提及,但确实具有)。该成员没有构造函数,因此在我看来它只是“垃圾”内存。实际上,我原本想要 64 个实例在垃圾内存上,即未初始化的内存。
    猜你喜欢
    • 1970-01-01
    • 2010-09-24
    • 2017-11-23
    • 2016-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多