使用默认构造函数强制未初始化的成员声明

Tob*_*emi 10 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;
}
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)

在这段代码中,您实际上可以members完全构造后简单地将变量向下移动;但是,在类中,这是不可能的,因为必须在声明时初始化具有默认构造函数的成员 - 尽管没有默认构造函数的成员不会以相同的方式受到限制。

在上述情况下,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 已经能够很好地实现。

Nic*_*las 5

重要的是要记住 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,但您可以创建一个新Toptional::emplace。并且您可以访问T->或这样的指针语法*。但它从不堆分配T,所以你没有那个开销。


Joh*_*nck 2

任何主流的C++编译器都没有这样的功能。我怎么知道?因为它会破坏(或警告)基本上每个现有的 C++ 库。你所要求的不存在,而且不能存在于编译 C++ 的编译器中。

  • 它不会破坏任何代码,未初始化的声明可以简单地选择加入。即类似“成员成员 = 删除” (2认同)
  • 我懂了。现在您要求将新功能添加到 C++ 语言中。您可以为相应的 C++ 工作组编写提案。但说实话,我认为这不会获得太多支持。从一种语言转移到另一种语言总是有点棘手,人们倾向于期望所有自己喜欢的习语都会在新语言中得到支持...... (2认同)