具有私有构造函数和静态自身数组的类

8

如果标题让您感到困惑,对不起。我无法找到一种简单的方式以简单的句子来表达它。不管怎样,我面临的问题是:

 // 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

我不能使用std::array of Something,除非我在类中添加一个公共构造函数SomeThing()。传统的数组可以正常工作,我可以定义数组,并在SomeThing::generateLookupTables()函数中填充。显然,类型std::array<SomeThing, 64>没有构造函数。有什么办法让它工作,或者这个概念的更好结构?
============= 编辑 =======
friend std::array<SomeThing, 64>方法似乎是一个不错的想法,但是:
它也将在其他地方使用数组。我希望保证这个类始终对外部用户保持某些不变量。使用此友好数组,用户可能会意外创建未初始化的SomeThing数组。
此外,查找表是使用相当复杂的过程生成的,不能像std::array<SomeThing, 64> SomeThing::lookup_table_0(some value)那样执行行内操作。

如果你的类是可移动的,考虑使用 std::vector。如果它不可移动,我认为只要你使用 emplace,std::deque 仍然可以工作。 - Brian Bi
我想知道这个类定义是否有问题,标准容器只能使用完整类型进行实例化。(而且这个问题只是由于类包含一个来自自身模板的静态成员引起的) - M.M
Matt: 标准容器在头文件中尚未实例化,而是在实现中实例化。 - Gábor Buella
这个问题很有趣,因为这是一个情况,C风格的数组不能被std::array简单地替换。 - M.M
我认为我有另一个半成品解决方案:如果C++在这方面与C的工作方式相同,那么全局变量(如Something :: lookup_table_0(不在任何函数的堆栈中,因此是全局的))将初始化为清零的内存。 因此,如果我制作一个仅将“some_data”设置为零的私有构造函数,并使用它来值初始化std :: array,那就基本上是一样的。 但这仍然是一种hack。 - Gábor Buella
3个回答

5

std::array<SomeThing, 64>类在尝试定义实例时,显然没有访问private默认构造函数的权限。您可以通过添加以下代码为其提供必要的访问权限:

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

涉及到SomeThing的定义。


4

由于您的构造函数是私有的,std::array 无法使用它。

您可以在 SomeThing 中添加 friend class std::array<SomeThing, 64>; 来允许访问构造函数。

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

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

编辑:

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

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

让您的整个数组进行默认初始化。


谢谢,但是我看到那样会暴露私有构造函数。请参考我在问题中的编辑。 - Gábor Buella
@BuellaGábor:看起来我找到了神奇的解决方案 :) - Jarod42
哇,那真聪明。我认为它实际上创建了一个实例,将其保留为未初始化的垃圾内存,并复制了另外63个该垃圾内存的副本。这有点愚蠢,但是从技术上讲“可行”。 - Gábor Buella
@MattMcNabb 我在代码中添加了一个成员变量。那个int就是我所指的垃圾内存。 - Gábor Buella
@BuellaGábor 好的,我明白你的意思。尽管其中的int没有被初始化,但这些对象仍然被认为是已经初始化了。 - M.M
显示剩余4条评论

4
解决方案:
std::array<SomeThing, 64> SomeThing::lookup_table_0 {{ }};

注意: 如在这里所解释的那样,{{}} 是在gcc中不警告地对std::array进行值初始化的必需形式。 = {}{} 虽然正确,但是gcc仍会发出警告。

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


首先进行术语检查:在C++中,所有对象都会被初始化。这有三种形式:默认。没有"未初始化"的对象;没有显式初始化程序的对象称为默认初始化对象。在某些情况下,这意味着对象的成员变量可能是不确定的("垃圾")。

没有初始化程序版本的问题是什么?首先,std::array<SomeThing, 64>的构造函数被定义为已删除,因为声明std::array<SomeThing, 64> x;将是非法的(当然是由于缺乏可访问的SomeThing默认构造函数)。

这意味着任何试图使用std::array<SomeThing, 64>的默认构造函数的代码也将是非法的。定义如下:

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

尝试使用默认构造函数会导致其不符合形式,因此是不正确的。但是一旦引入初始化程序,std::array的默认构造函数就不再起作用了;由于std::array是一个聚合体,因此会发生聚合初始化,从而绕过了隐式生成的构造函数。(如果有任何用户声明的构造函数,则它将不再是一个聚合体)。
带有初始化程序的版本之所以有效,是因为[dcl.init]/13 (n3936):
"静态成员的初始化程序在成员类的范围内"
这里的列表初始化定义将{ }转换为{ SomeThing() }

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Gábor Buella
这基本上与另一个解决方案 lookup_table_0( SomeThing() } 是一样的。未初始化和值初始化之间有区别。无论如何,现在我想我会继续使用旧式数组。 - Gábor Buella
经过一番思考,我认为可以将此方案作为解决方案接受,并期望编译器优化程序可以识别出无需执行任何操作。顺便提一下,我刚试用了这个构造函数:SomeThing(){ std::cout << "ctor here\n"; }它在你的示例lookup_table_0 {{ }}中打印了"ctor here\n" 64次,但是在使用C风格数组时也打印了相同的64次。本来我想找的是“一个大小为64*sizeof(SomeThing)的内存块”这个概念,而不是运行构造函数64次,但是我猜结果对于任何一个好的编译器来说都是一样的。 - Gábor Buella
好的。当您准备好时,可以分配一块内存并在其上使用放置new,但这会更加混乱。如果您的默认构造函数实际上什么也不做,它将被优化掉。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接