【问题标题】:C++ primer 5th edition switch statementC++ 入门第 5 版 switch 语句
【发布时间】:2021-11-24 11:37:08
【问题描述】:

在C++入门第5版的解决方案附录中,给出了一个关于switch语句的例子:

 case true:
     string file_name; // error: control bypasses an implicitly initialized variable.
     ...
 case false:
     if(file_name.empty()); // file_name is in scope but wasn't initialized.

但我认为这是错误的,因为 file_name 已被隐式初始化,这就是编译器标记该错误的原因。

struct Foo{};
switch(val){
    case 1:
       Foo f; // ok not-implicitly initialized like std::string
    break;
    case 2:
        f.some_member(); // use f;
}

所以我的猜测正确吗?

【问题讨论】:

  • 什么是“隐式初始化”?
  • @StoryTeller-UnslanderMonica:file_name,其类型为 std::string。我认为没有像Foo f{}; 这样的显式值初始化。书中有“隐式初始化”这个表达。
  • 如果书中有“隐式初始化”,要么是没有正确定义,要么是这里有一些误解。在那个领域之外,这是一个毫无意义的术语。也许这里的意思是“默认初始化”?
  • 我推测这本书中“隐式初始化”的意思是调用了默认构造函数。
  • switch 语句及其case 标签是结构化的gotocase 标签没有自己的范围,尽管您可以放入自己的 case 0: { ... } break; 以提供自己的范围块。在一个 case 标签中定义的变量,但它绕过了 构造,因为 switch 值转到后续的 case 标签将不会被构造。我自己的建议是:不要那样做。 (C++ 给了你足够的绳索让你在脚上开枪。)

标签: c++ scope switch-statement declaration


【解决方案1】:

例如根据 C++ 17 标准(9.7 声明语句)

3 可以转移到一个块中,但不能以某种方式转移 通过初始化绕过声明。一个从 91 跳转的程序 具有自动存储持续时间的变量不在的点 范围到它在范围内的点是格式错误的,除非 变量具有标量类型,具有平凡默认值的类类型 构造函数和普通析构函数,其中之一的 cv 限定版本 这些类型,或上述类型之一的数组,并被声明 没有初始化器

std::string 类没有普通的默认构造函数和普通的析构函数。

在这段代码中sn-p

struct Foo{};
switch(val){
    case 1:
       Foo f; // ok not-implicitly initialized like std::string
    break;
    case 2:
        f.some_member(); // use f;
}

Foo 类有一个普通的默认构造函数和一个普通的析构函数。

例如(15.1 构造函数)

6 如果默认构造函数不是用户提供的并且满足以下条件,则它是微不足道的:

(6.1) — 它的类没有虚函数 (13.3) 也没有虚基 类 (13.1) 和

(6.2) — 其类的非静态数据成员没有默认成员 初始化程序(12.2),和

(6.3) — 其类的所有直接基类都具有平凡的默认值 构造函数,以及

(6.4) — 对于其类的所有非静态数据成员 类类型(或其数组),每个这样的类都有一个微不足道的默认值 构造函数。

否则,默认构造函数是不平凡的。

【讨论】:

  • “jumps91”是什么意思?还是错字?
  • @ItachiUchiwa 只是缺少格式。查看标准:)
  • @ItachiUchiwa 91 是对脚注的引用。:)
【解决方案2】:

“隐式初始化”一词听起来很专业。但它是gobbledygook。这意味着什么都没有

并且编译器不使用这个虚构的术语也不。也就是说:

int main()
{
    struct Foo {
        void some_member() {}
        int a = 1;
    };
    int val = 0;
    switch (val){
        case 1:
            Foo f;
            break;
        case 2:
            f.some_member(); // use f;
    }
}

编译器输出:

<source>: In function 'int main()':
<source>:12:14: error: jump to case label
   12 |         case 2:
      |              ^
<source>:10:17: note:   crosses initialization of 'main()::Foo f'
   10 |             Foo f;
      |                 ^

仅此而已。初始化,简单明了。当您删除成员 Foo::a 的初始化程序时,将不再进行初始化,编译器会接受:

int main()
{
    struct Foo {
        void some_member() {}
        int a;
    };
    int val = 0;
    switch (val){
        case 1:
            Foo f; // OK - no initialization at all
            break;
        case 2:
            f.some_member(); // use f
            if (f.a) {};     // undefined behavior, a is uninitialized here
    }
}

现在,f.a 没有初始化,这没关系,除非您尝试使用它的值。对f.a 的访问不会导致编译时失败,而是未定义的行为,就像您编写了以下内容一样:

int main() {
   int a;
   if (a) {} // undefined behavior, a isn't initialized
}

现代编译器可以并且将使用未初始化的变量/成员访问作为优化提示。例如,可以删除使用未初始化值的代码。

【讨论】:

  • OK 真的明白但是在关于字符串对象的书中:if (file_name.empty()); // “file_name 在范围内但未初始化”。它是默认初始化的,不是吗? (它是 std::string 类型)。所以这本书是错的?
  • 由于 std::string 的代码不是有效的 C++,所以谈论任何在范围内的东西都是无稽之谈。您不能将无效程序视为有效程序进行分析。没有范围,因为范围也是为至少可以编译的有效 C++ 定义的技术术语。您不是在谈论某种抽象编程语言中的范围,而是在 C++ 程序中谈论范围。而且你那里没有 C++ 程序,甚至没有一个(!)的一部分。 C++ Primer 充满了这样的细微错误,因此恕我直言,这是一本非常令人困惑的书。
  • @ItachiUchiwa 不,file_name 没有在case false: 中初始化,只是声明了。
  • @S.M.它没有被声明或其他任何与 C++ 相关的术语。代码 sn-p 无效,因此尝试这样分析它是没有意义的,只会增加混乱。在这种情况下,file_name 是一些在 C++ 中没有意义的文本。您此时正在讨论幻想。
  • 错误消息肯定说它 crosses initialization of file_name, not file_name is undefined .
猜你喜欢
  • 2021-09-04
  • 1970-01-01
  • 2021-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多