草庐IT

c++ - C/C++ 中可中断的命名范围

coder 2024-02-12 原文

介绍

这个问题来自这个问题:The named loop idiom : dangerous? .对于不想阅读原始问题的人来说,这是关于做这样的事情:

named(label1) for(int i = 0 ; i < 10 ; i++) {
    for(int j = 0 ; j < 10 ; j++) {
      if(some_condition)
            break(label1); // exit outer loop
      }
}

这个新问题是关于“命名循环”习语的改进版本。如果你懒得阅读整篇文章,你可以直接进入这篇文章的“示例”部分,清楚地理解我在说什么。

设计缺陷

不幸的是,这个问题很快就结束了(后来又重新打开了),因为它更像是一个利弊辩论,而不是一个纯粹的技术问题。似乎它不适合 SO Q&A 格式。此外,我提供的代码有几个缺陷:
  • 关键词break由宏重新定义
  • 宏是用小写字母写的
  • 它使一些可怕的东西可以编译(至少使用 MSVC):
    int foo() {
    
       named(label1) for(int i = 0 ; i < 10; i++)
       {
          if(some_condition)
          {
              break(label1);  // here it's ok, the behavior is obvious
          }   
       }
    
       break(label1); // it compiles fine without warning... but the behavior is pretty obscur!
    
    }
    
  • 它可能会破坏一些好看的代码。例如,由于范围问题,以下内容未编译。
    int foo() {
    
    named(label1)  for(int i = 0 ; i < 10 ; i++)
           named(label2)  for(int j = 0 ; j < 10 ; j++)
            if(i*j<15)
                cout << i*j << endl;
            else
                break(label2);
     }
    

  • 更安全的实现

    我试图解决所有这些问题以获得 的安全版本命名循环 .更一般地说,它也可以称为 易碎范围 因为它可以用于提前退出任何范围,而不仅仅是循环。

    这里是两个宏的定义NAMEDBREAK :
        #define NAMED(bn)   if(const bool _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_ = true)\
                                goto _named_loop_identifier__##bn##_;\
                            else\
                                _break_the_loop_labelled_##bn##_:\
                                if(true)\
                                    {}\
                                else\
                                    if(! _YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_)\
                                        goto _break_the_loop_labelled_##bn##_;\
                                    else\
                                        _named_loop_identifier__##bn##_:\
    
    
        #define BREAK(bn) if(!_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_##bn##_){} \
                            else goto _break_the_loop_labelled_##bn##_
    

    它看起来丑陋且笨重,因为它还避免了可能由 MSVC 或 GCC 生成的一些警告,如“未使用的变量”、“未引用的标签”或“建议显式大括号”。此外,如果使用不当,它不应编译,在这种情况下,错误消息将是可以理解的。例如 :
        NAMED(loop1) for(int i = 0 ; i < 10; i++) {
            NAMED(loop2) for(int j = 0 ; j < i ; j++) {
                cout << i << "," << j << endl;
                if(j == 5) {
                    BREAK(loop1);   // this one is okay, we exit the outer loop
                }
            }
            BREAK(loop2); // we're outside loop2, this is an error
        }
    

    前面的代码不会编译,第二个的编译器错误信息BREAK :'_YOU_CANNOT_USE_NAMED_BREAK_IF_YOU_ARE_OUTSIDE_loop2_`,这是非常明确的。

    例子

    在问我的问题之前,我提供了两个例子来说明这些结构的(相对)有用性:
    break一个外循环:
         NAMED(myloop) for(int i = 0 ; i < rows; i++) {
             for(int j = 0 ; j < cols ; j++) {
                if(some_condition) {
                     BREAK(myloop);
                }
             }
         }
    

    退出特定范围:
    NAMED(myscope1) {
        cout<< "a";
        NAMED(myscope2)
        {
            cout << "b";
            NAMED(myscope3)
            {
                cout << "c";
                BREAK(myscope2);
                cout << "d";
            }
            cout << "e";
        }
        cout <<"f";
    }
    

    此代码打印:abcf .

    我的问题

    在定义我的问题之前,我必须定义它不是什么,因为我不想看到我的主题在 10 分钟内结束。

    不是 “这是个好主意吗?”,也不是“你觉得怎么样?”甚至“它有用吗?”,因为 stackoverflow 似乎不是一个讨论场所。无论如何,我已经知道答案:“宏是邪恶的”、“Goto 是邪恶的”和“改用 lambdas”。

    相反,我的问题是关于技术正确性和针对编程错误的鲁棒性。我希望这个构造尽可能安全。
  • 用户是否有可能滥用它并且仍然能够编译?
    我试图修复原始实现的明显问题,但 C++ 非常复杂,也许我错过了什么?
  • 它可以默默地破坏一些好看的代码吗? 这是我主要关心的问题。它是否会干扰其他 C++ 功能(异常处理、析构函数调用、其他条件语句或其他任何东西...)?

  • 我的目标是证明这种结构本质上并不危险。我已经知道在实际代码中使用它是一个非常糟糕的主意,因为其他程序员可能不清楚它,但是在个人项目中使用它是否足够安全?

    编辑: bool 变量现在是 const (感谢 Jens Gustedt)。我会尝试更换 if来自 for稍后检查它可以在像这样使用时删除虚假警告:
    if(true)
         BREAK(label);
    

    EDIT2:正如 JensGustedt 所注意到的,if 中的变量声明C 语言中不允许使用语句(仅限 C++)。用循环替换它的另一个原因。

    最佳答案

    我想你已经测试过了,但是......不是bool您在 NAMED(bn) 中声明的变量稍后在同一块中仍然可用它声明的 if 语句是否存在? (:除非是这样,否则您的习语将不起作用。:)

    我可以看到这是安全的:

    {
        NAMED(one) { ... }
    }
    BREAK(one);
    

    编译器会在BREAK(nb);上大惊小怪线

    但这看起来仍然不安全:
    {
        NAMED(bn) { ... }
        BREAK(bn);
    }
    

    不安全,因为编译器可能仍然接受定义的变量而不是大惊小怪。但它可能会形成一个无限循环,从而无声地破坏您的程序。

    -杰西

    PS:不会干扰try..finallyfinally块被定义为无论您如何退出 try 都会执行堵塞。所以只要你不是 正在尝试 避免 finally块,你没事。

    P(PS)S:我看到的与其他结构唯一真正奇怪的交互是这样的:
    #if DEBUG
        NAMED(bn)
    #endif
        while(true)
        {
            BREAK(BN);
        }
    

    这是病态的! ;-D 这会在 DEBUG 中编译正常,但在 RELEASE 中会出现编译器错误。

    关于c++ - C/C++ 中可中断的命名范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11072212/

    有关c++ - C/C++ 中可中断的命名范围的更多相关文章

    1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    2. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

      请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

    3. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

      我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

    4. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

      我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

    5. ruby-on-rails - 如何重命名或移动 Rails 的 README_FOR_APP - 2

      当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?

    6. Ruby 从大范围中获取第 n 个项目 - 2

      假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

    7. ruby - rails 3 redirect_to 将参数传递给命名路由 - 2

      我没有找到太多关于如何执行此操作的信息,尽管有很多关于如何使用像这样的redirect_to将参数传递给重定向的建议:action=>'something',:controller=>'something'在我的应用程序中,我在路由文件中有以下内容match'profile'=>'User#show'我的表演Action是这样的defshow@user=User.find(params[:user])@title=@user.first_nameend重定向发生在同一个用户Controller中,就像这样defregister@title="Registration"@user=Use

    8. ruby - 使用 `+=` 和 `send` 方法 - 2

      如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

    9. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

      我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

    10. sql - 查询忽略时间戳日期的时间范围 - 2

      我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

    随机推荐