【发布时间】:2020-06-13 00:27:05
【问题描述】:
我今天发现了这种现象,一个成员被不必要地构造了两次:
#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;
}
有没有办法声明成员 uninitialised - 而不是使用默认构造函数 - 从而迫使您在构造函数中使用初始化列表?
在 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;
在这段代码中,您实际上可以在 s 完全构造后简单地将 member 变量向下移动;然而,在一个类中,这是不可能的,因为具有默认构造函数的成员必须在声明时初始化 - 尽管没有默认构造函数的成员不受相同方式的限制。
在上述情况下,std::string 的默认构造函数的冗余使用相对便宜,但这并不适用于所有情况。
我不希望默认构造函数消失,我只想要一个选项让成员在构造函数之前保持未初始化 - 就像我可以使用没有默认构造函数的类型一样。对我来说,这似乎是一个如此简单的功能,我对为什么不支持它感到困惑/
如果不是支持无括号的类的无括号实例化,这似乎会自然地被实现(只要没有默认构造函数的类型的未初始化声明),它假定实例化类 - 即使当你希望它们未初始化,就像我的情况一样。
编辑:再次遇到这个问题
在java中你可以做到这一点
int x; // UNINITIALISED
if (condition){
x = 1; // init x;
}
else return;
use(x); // INITIALISED
在 c++ 中这是不可能的???
它使用默认构造函数进行初始化,但这不是必需的——它很浪费。
- 注意:您不能使用未初始化的变量。
正如你所看到的,因为我在循环之外使用x,所以它必须在那里声明,此时它被 - 不必要地 - 初始化。
int x = delete 会很有用的另一种情况。它不会破坏任何代码,并且只会在尝试使用未初始化的 x 时导致编译时错误。
没有未初始化的内存或不确定的状态,它只是编译时的事情 - Java 已经能够很好地实现。
【问题讨论】:
-
评论不用于扩展讨论;这个对话是moved to chat。
标签: c++