【问题标题】:Function Call Guard函数调用保护
【发布时间】:2009-07-21 17:16:58
【问题描述】:

假设我有一个名为InitFoo 的免费函数。我想保护这个函数不被意外调用多次。没有多想,我写了以下内容:

void InitFoo()
{
    {
        static bool flag = false;
        if(flag) return;
        flag = true;
    }

    //Actual code goes here.
}

不过,这看起来像个大疣。 InitFoo 不需要需要保留任何其他状态信息。有人可以建议一种方法来实现相同的目标而不会丑陋吗?

当然,宏不算数。

【问题讨论】:

  • 无论您最终实施哪种解决方案,都选择它作为正确答案。如果另一个答案更受欢迎,它会在其下方冒泡。

标签: c++ function call guard


【解决方案1】:

你可以用一些不同的丑来做:

struct InitFoo
{
     InitFoo()
     {
         // one-time code goes here
     }
};

void Foo()
{
    static InitFoo i;
}

您仍在使用static,但现在您不需要进行自己的标志检查 - static 已经放入一个标志并对其进行检查,因此它只构造一次i

【讨论】:

  • 不认为有任何机会伴随反对票发表评论?
  • 嗯。我仍在尝试撤消我的意外点击。有什么提示吗?
  • 哈哈,看来你想通了!
  • 啊,我才发现。很抱歉造成混乱。
【解决方案2】:

好吧,构造函数只会自动调用一次。如果您创建此类的单个实例:

class Foo
{
public:
    Foo(void)
    {
        // do stuff
    }
}

那么//do stuff 只会执行一次。执行它两次的唯一方法是创建该类的另一个实例。

您可以使用Singleton 来防止这种情况。实际上,//do stuff 只能被调用一次。

【讨论】:

  • +1 这是使用单例的正确时间的完美示例。
【解决方案3】:

我想保护这个函数不被意外调用多次

对我来说,这听起来像是一个只会在调试期间出现的问题。如果是这种情况,我会简单地执行以下操作:

void InitFoo()
{
    #ifndef NDEBUG
       static bool onlyCalledOnce = TRUE;
       assert(onlyCalledOnce);
       onlyCalledOnce = FALSE;
    #endif

    ...
}

这个特殊缺陷的目的很容易通过查看它来辨别,如果程序员犯了不止一次调用InitFoo 的错误,它将导致一个漂亮的、大的、华丽的断言失败。它也将在生产代码中完全消失。 (当NDEBUG 被定义时)。

编辑:关于动机的简要说明:
多次调用 init 函数可能是一个很大的错误。如果这个函数的最终用户错误地调用了它两次,那么默默地忽略这个错误可能不是正确的方法。如果您不走assert() 路线,我建议至少将消息转储到stdoutstderr

【讨论】:

  • 好电话,但我认为您的意思是#ifndef。 ;)
  • 一个奇怪的动机 - 如果可能的话,模块自动处理初始化自己肯定更方便吗?例如,参见 std::cout 等。
  • @Earwicker:绝对正确。该问题指定 InitFoo 是一个“免费功能”,我在回答时考虑了这一限制。感谢您(和 +1)看到更大的问题,并提供了一个优雅的解决方案。
【解决方案4】:

我就是这样做的。如果你想要一个替代方案,你可以使用一些函数指针改组:

static void InitFoo_impl()
{
    // Do stuff.

    // Next time InitFoo is called, call abort() instead.
    InitFoo = &abort;
}

void (*InitFoo)() = &InitFoo_impl;

【讨论】:

  • 可以从多个线程调用两次——如果这是个问题的话。
  • 漂亮。不过有一件事:例如,像 __InitFoo 这样的名称是为实现保留的。
  • @MeadP:已修复。它已经是静态的了;认为将其放在命名空间中没有意义。
【解决方案5】:

您还需要它是多线程安全的吗?查看带有双重检查锁定的单例模式(这很容易出错)。

如果你不想要一个完整的班级,另一种简单的方法是:

在 .cpp 中(不要在 .h 中声明 InitBlah)

 // don't call this -- called by blahInited initialization
static bool InitBlah() 
{
   // init stuff here
   return true;
}
bool blahInited = InitBlah();

没有人可以在这个 .cpp 之外调用它,它会被调用。当然,有人可以在这个 .cpp 中调用它——这取决于你有多在意它是不可能的还是不方便的和记录在案的。

如果您关心订单或在特定时间进行操作,那么 Singleton 可能适合您。

【讨论】:

  • 最好不要为此目的滚动您自己的双重检查锁定。除了在某些架构上它根本无法工作的事实之外,即使使用 volatile 变量,这也是 pthread_once、InitOnceExecuteOnce 等的用途。
【解决方案6】:

在需要一次性但不值得制作一个全班的情况下,我总是这样做。当然,它假设您不担心与线程相关的问题。我通常在变量名前加上“s_”,以表明它是一个静态变量。

【讨论】:

    【解决方案7】:

    嗯...如果你不反对使用Boost,那么看看boost::call_once

    namespace { boost::once_flag foo_init_flag = BOOST_ONCE_INIT; }
    
    void InitFoo() {
        // do stuff here
    }
    
    void FooCaller() {
        boost::call_once(&foo_init_flag, InitFoo);
        // InitFoo has been called exactly once!
    }
    
    void AnotherFooCaller() {
        boost::call_once(&foo_init_flag, InitFoo);
        // InitFoo has been called exactly once!
    }
    

    【讨论】:

      【解决方案8】:

      并不是我对此很兴奋,但这只是另一种方式:函数对象。

      #import <iostream>
      
      class CallOnce {
      private:
          bool called;
      public:
          CallOnce() {
              called = false;
          }
          void operator()(void) {
              if (called) {
                  std::cout << "too many times, pal" <<std::endl;
                  return;
              }
              std::cout << "I was called!" << std::endl;
              called = true;
          }
      
      };
      
      int main(void) {
          CallOnce call;
      
          call();
          call();
      }
      

      【讨论】:

        猜你喜欢
        • 2014-11-03
        • 2020-08-10
        • 1970-01-01
        • 2017-12-12
        • 1970-01-01
        • 2018-03-03
        • 2021-09-24
        • 1970-01-01
        • 2015-08-26
        相关资源
        最近更新 更多