c++ - 使用默认构造函数强制未初始化的成员声明
问题描述
我今天发现了这种现象,其中一个成员被不必要地构造了两次:
#include <iostream>
class Member {
public:
Member() {
std::cout << "Created member (default)" << std::endl;
}
Member(int i) {
std::cout << "Created member: " << i << std::endl;
}
};
class Object {
Member member;
public:
Object() {
member = 1;
}
};
int main() {
Object o;
return 0;
}
有没有办法声明成员未初始化- 而不是使用默认构造函数 - 从而迫使您在构造函数中使用初始化列表?
在Java中,如果你像这样定义一个成员:Member i;
并且你没有在每个构造函数中初始化它,你会在尝试使用它时得到一个错误,说该字段可能未初始化。
如果我从类中删除默认构造函数Member
,我会得到我想要的行为——编译器会强制你为每个构造函数使用一个初始化列表——但我希望这通常发生,以防止我忘记使用这种形式(当默认构造函数可用时)。
本质上,我想防止错误地使用默认构造函数,但看起来这不存在......
explicit
即使用关键字标记构造函数,Member member
仍然会生成一个成员 - 当它在构造函数中重新分配时会立即被丢弃。这本身似乎也不一致......
我的主要问题是不一致。如果没有默认构造函数,您可以声明未初始化的成员;这实际上很有用;您不需要提供初始冗余声明,而只需在构造函数中初始化(如果未初始化则中断)。具有默认构造函数的类完全缺少此功能。
一个相关的例子是:
std::string s;
s = "foo";
您可以简单地这样做:std::string s = "foo";
相反,如果"foo"
实际上是多行 - 而不是单个表达式 - 我们会得到非原子初始化。
std::string s = "";
for (int i = 0; i < 10; i++) s += i;
这种初始化很容易以撕裂的方式结束。
如果像这样拆分它,它几乎是原子分配的,但是您仍然可以将默认值用作占位符:
std::string member;
// ...
std::string s = "";
for (int i = 0; i < 10; i++) s += i;
member = s;
在这段代码中,您实际上可以在完全构造member
后简单地将变量向下移动;s
然而,在一个类中,这是不可能的,因为具有默认构造函数的成员必须在 decleration 时初始化——尽管没有默认构造函数的成员不受相同方式的限制。
在上述情况下,冗余使用std::string
' 的默认构造函数相对便宜,但这并不适用于所有情况。
我不希望默认构造函数消失,我只想要一个选项让成员在构造函数之前保持未初始化 - 就像我可以使用没有默认构造函数的类型一样。对我来说,这似乎是一个如此简单的功能,我对为什么不支持它感到困惑/
如果不是支持无括号的类的实例化,这似乎自然会实现(只要没有默认构造函数的类型的未初始化声明),它假定实例化类 - 即使您希望它们未初始化,就像我的情况一样。
编辑:再次遇到这个问题
在java中你可以这样做
int x; // UNINITIALISED
if (condition){
x = 1; // init x;
}
else return;
use(x); // INITIALISED
在 C++ 中这是不可能的???它使用默认构造函数进行初始化,但这不是必需的——它很浪费。
- 注意:你不能使用未初始化的变量。正如你所看到的,因为我x
在循环之外使用,它必须在那里声明,此时它 - 不必要地 - 初始化。int x = delete
另一个有用的场景。它不会破坏任何代码,并且只会在尝试使用未初始化的 x 时导致编译时错误。
没有未初始化的内存或不确定的状态,它只是编译时的事情——Java 已经能够很好地实现。
解决方案
重要的是要记住 C++ 不是 Java。在 C++ 中,变量是对象,而不是对对象的引用。当你在 C++ 中创建一个对象时,你就创建了一个对象。调用默认构造函数来创建对象与调用任何其他构造函数一样有效。在 C++ 中,一旦进入类构造函数的主体,它的所有成员子对象都是完整的对象(至少,就语言而言)。
如果某些类型具有默认构造函数,则意味着您可以 100% 使用该默认构造函数来创建该类型的实例。这样的对象不是“未初始化的”;它通过其默认构造函数进行初始化。
简而言之,您认为默认构造的对象“未初始化”或以其他方式无效是错误的。除非该默认构造函数显式地使对象处于非功能状态,否则不会。
我不希望默认构造函数消失,我只想要一个选项让成员在构造函数之前保持未初始化 - 就像我可以使用没有默认构造函数的类型一样。
同样,C++ 不是 Java。C++ 中的“未初始化”一词的含义与处理 Java 时完全不同。
Java 声明引用,C++ 声明对象(和引用,但它们必须立即绑定)。如果一个对象“未初始化”,它仍然是C++ 中的一个对象。该对象具有未定义的值,因此您访问它的方式受到限制。但就 C++ 的对象模型而言,它仍然是一个完整的对象。你不能在以后构建它(不是没有placement-new)。
在 Java 中,未初始化变量意味着没有对象;这是一个空引用。C++ 没有等效的语言概念,除非所讨论的成员是指向对象的指针而不是对象本身。这是一个相当重量级的操作。
无论如何,在 C++ 中,类的作者有权限制该类的工作方式。这包括它是如何被初始化的。如果一个类的作者想要确保该对象中的某些值始终被初始化,那么他们就可以这样做,而您无法阻止它。
一般来说,你应该避免尝试做你正在做的事情。但是,如果您必须在构造函数成员初始值设定项列表之外初始化某些类型,并且您不想调用其默认构造函数(或者它没有),那么您可以使用std::optional<T>
,有T
问题的类型在哪里. optional
听起来是这样的:一个可能持有也可能不持有T
. 它的默认构造函数以不带 . 开头T
,但您可以T
使用optional::emplace
. 您可以访问T
with 指针语法,如->
or *
。但它从不堆分配T
,所以你没有那个开销。
推荐阅读
- javascript - 从php mysql循环json数据获取javascript函数
- sql-server - 错误:在贡献表中未找到以下列
- arrays - 查找多个峰值数组,最佳算法
- html - 字体真棒图标不出现
- rocket-chip - 默认缓存参数,如何更改,限制等
- python - 使用 click 设计一个 CLI,它支持具有无限参数的多个参数
- firebase - 将图像保存到 Firebase 存储桶没有链接上的身份验证
- fortran - 如何在while循环中更改fortran程序索引变量?
- tensorflow - Keras 在自定义损失函数中使用 class_weights 吗?
- r - 为什么我看到 R 向量类的“整数”而不是“向量”