我今天发现了这个现象,其中一个成员被不必要地构造了两次:
#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;
}
Run Code Online (Sandbox Code Playgroud)
有没有办法声明成员未初始化- 而不是使用默认构造函数 - 从而迫使您在构造函数中使用初始化列表?
在 Java 中,如果您像这样定义一个成员:Member i;并且您没有在每个构造函数中初始化它,那么在尝试使用它时,您将收到一个错误,指出该字段可能未初始化。
如果我从Member类中删除默认构造函数,我会得到我想要的行为 - 编译器强制您为每个构造函数使用初始化列表 - 但我希望这通常发生,以防止我忘记使用这种形式(当默认构造函数可用时)。
本质上,我想要防止错误地使用默认构造函数,但它看起来不存在......
即使使用explicit关键字标记构造函数,Member member仍然会生成一个成员 - 在构造函数中重新分配时会立即丢弃该成员。这本身似乎也不一致......
我的主要问题是不一致。如果没有默认构造函数,您可以声明一个未初始化的成员;这实际上很有用;您不需要提供初始冗余声明,而只需在构造函数处进行初始化(如果未初始化则中断)。对于具有默认构造函数的类,此功能完全缺失。
一个相关的例子是:
std::string s;
s = "foo";
Run Code Online (Sandbox Code Playgroud)
你可以简单地做:std::string s = "foo";相反,如果"foo"实际上是多行——而不是单个表达式——我们得到非原子初始化。
std::string s = "";
for (int i = 0; i < 10; i++) s += i;
Run Code Online (Sandbox Code Playgroud)
这种初始化很容易以撕裂的写入结束。
如果你把它分开,像这样,它几乎是原子分配的,但是你仍然使用默认值作为占位符:
std::string member;
// ...
std::string s = "";
for (int i = 0; i < 10; i++) s += i;
member = s;
Run Code Online (Sandbox Code Playgroud)
在这段代码中,您实际上可以member在s完全构造后简单地将变量向下移动;但是,在类中,这是不可能的,因为必须在声明时初始化具有默认构造函数的成员 - 尽管没有默认构造函数的成员不会以相同的方式受到限制。
在上述情况下,std::string的默认构造函数的冗余使用相对便宜,但这并不适用于所有情况。
我不希望默认构造函数消失,我只想要一个选项,让成员在构造函数之前未初始化 - 就像我可以使用没有默认构造函数的类型一样。对我来说,这似乎是一个如此简单的功能,我对为什么不支持它感到困惑/
如果不是支持类的无括号实例化,这似乎会自然地实现(每当未初始化没有默认构造函数的类型声明时),这冒昧地实例化类 - 即使您希望它们未初始化,就像我的情况一样。
编辑:再次遇到这个问题
在java中你可以这样做
#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;
}
Run Code Online (Sandbox Code Playgroud)
在 C++ 中这是不可能的???它使用默认构造函数初始化,但这不是必需的 - 它很浪费。
- 注意:您不能使用未初始化的变量。正如你所看到的,因为我x在循环之外使用,它必须在那里声明,此时它 - 不必要地 - 初始化。另一个int x = delete有用的场景。它不会破坏任何代码,并且只会在尝试使用未初始化的 x 时导致编译时错误。
没有未初始化的内存或不确定的状态,它只是编译时的事情——Java 已经能够很好地实现。
重要的是要记住 C++ 不是 Java。在 C++ 中,变量是对象,而不是对对象的引用。当您在 C++ 中创建对象时,您已经创建了一个对象。调用默认构造函数来创建对象与调用任何其他构造函数一样有效。在 C++ 中,一旦你进入一个类的构造函数的主体,它的所有成员子对象都是完全形成的对象(至少,就语言而言)。
如果某些类型具有默认构造函数,则意味着您可以 100% 使用该默认构造函数来创建该类型的实例。这样的对象不是“未初始化的”;它通过其默认构造函数初始化。
总之,这是错误的,你要考虑一个默认的构造的对象“未初始化”或否则无效。除非默认构造函数显式地使对象处于非功能状态。
我不希望默认构造函数消失,我只想要一个选项,让成员在构造函数之前未初始化 - 就像我可以使用没有默认构造函数的类型一样。
同样,C++ 不是 Java。C++ 中的术语“未初始化”的含义与您在处理 Java 时完全不同。
Java 声明引用,C++ 声明对象(和引用,但它们必须立即绑定)。如果一个对象是“未初始化的”,它在 C++ 中仍然是一个对象。该对象具有未定义的值,因此您访问它的方式受到限制。但就 C++ 的对象模型而言,它仍然是一个完整的对象。你不能在以后构建它(不是没有放置新的)。
在 Java 中,未初始化的变量意味着没有对象;这是一个空引用。C++ 没有等效的语言概念,除非所讨论的成员是指向对象的指针而不是对象本身。这是一个相当重量级的操作。
无论如何,在 C++ 中,类的作者有权限制该类的工作方式。这包括它如何被初始化。如果一个类的作者希望确保在该对象的特定值总是被初始化,然后他们得到这样做并没有什么可以阻止它这样做。
一般来说,你应该避免尝试做你正在做的事情。但是,如果您必须在构造函数成员初始值设定项列表之外初始化某种类型,并且您不想调用其默认构造函数(或者它没有),那么您可以使用std::optional<T>, where Tis the type of question . optional听起来是这样的:一个可能包含也可能不包含T. 它的默认构造函数开头没有T,但您可以创建一个新T的optional::emplace。并且您可以访问T像->或这样的指针语法*。但它从不堆分配T,所以你没有那个开销。
任何主流的C++编译器都没有这样的功能。我怎么知道?因为它会破坏(或警告)基本上每个现有的 C++ 库。你所要求的不存在,而且不能存在于编译 C++ 的编译器中。