【问题标题】:How to catch exception thrown while initializing a static member如何在初始化静态成员时捕获引发的异常
【发布时间】:2010-02-24 22:27:06
【问题描述】:

我有一个带有静态成员的类:

class MyClass
{
public:
    static const SomeOtherClass myVariable;
};

我在 CPP 文件中这样初始化:

const SomeOtherClass MyClass::myVariable(SomeFunction());

问题是,SomeFunction() 从注册表中读取一个值。如果该注册表项不存在,则会引发异常。这会导致我的程序在没有给用户任何有用输出的情况下爆炸...有什么方法可以捕获异常以便记录它吗?

【问题讨论】:

    标签: c++ exception-handling static


    【解决方案1】:

    我不太喜欢static数据成员,初始化的问题是最重要的。

    每当我必须进行重要处理时,我都会作弊并使用本地 static 代替:

    class MyClass
    {
    public:
        static const SomeOtherClass& myVariable();
    };
    
    const SomeOtherClass& MyClass::myVariable()
    {
      static const SomeOtherClass MyVariable(someOtherFunction());
      return MyVariable;
    }
    

    这样,异常只会在第一次使用时抛出,而对象将是const

    这是一个非常强大的延迟执行的成语。它有一点开销(基本上编译器每次进入方法时都会检查一个标志),但最好先担心正确性;)

    如果这是从多个线程调用的:

    • 如果你的编译器能处理它,没问题
    • 如果您的编译器没有,您可以使用本地线程存储(无论如何它都是 const)
    • 您可以在 Boost.Threads 库中使用 boost::once
    • 既然是const,你可能不关心它是否被多次初始化,除非someOtherFunction不支持并行执行(注意资源)

    指南:仅对简单对象(不能抛出)使用staticglobal 变量实例化,否则使用local static 变量来延迟执行,直到您可以捕获结果异常。

    【讨论】:

    • 仅仅因为它 const 并不意味着你不关心它被多次初始化。它可能是访问一个你真的只想接受一次打击的资源。 SomeOtherFunction 也可能需要很长时间才能运行。无论如何,您可能不希望它在 main 运行之前执行。
    • 没错,我只是想指出可能没有必要担心同步:const 意味着它一旦初始化就永远不会改变,而如果它不是(想想一个计数器,例如),使用后重置会搞砸。当然,由 OP 来权衡手头的风险......我将编辑答案以使最后一点更清楚。
    • 我很确定我的代码没有任何线程问题...应该在启动任何额外线程之前对其进行初始化。我已经实施了这个解决方案,它似乎解决了这个问题。谢谢!
    【解决方案2】:

    也许最好的做法是将注册表项添加到列表中,而不是查找它,然后在输入 main() 后立即查看并查找列表中的所有键。我不想说教,但是像这样的情况正是为什么在输入 main() 之前进行大量处理通常是一个坏主意的原因。

    【讨论】:

    • 问题在于我的对象是一个常量……它必须在那个时候被初始化。除非我使用 const_cast...
    • 没错,我忽略了这一点。您当然可以创建全局对象指针并在 main 中初始化它们,但这可能涉及大量代码更改。在这一点上它是学术性的......人们在其他答案中提出了一个实用的解决方法,我唯一的建议是如何避免 pre-main() 行为。然而,消除这种行为可能没有必要(尽管它也可以避免其他问题)。
    • @MSN,抱歉,我不确定您的意思... iostream 与问题或答案有什么关系?
    【解决方案3】:

    当然 -- 将 SomeFunction() 包装在如下函数中:

    int static_error;
    
    void SomeFunctionWrapper() { 
        try { 
            SomeFunction();
        }
        catch(...) { // something more specific if possible
            static_error = 1;
        }
    }
    

    然后在进入 main 时,您需要检查 static_error != 0 并在需要时打印适当的错误消息(不幸的是,您不知道 std::cerr 是否存在于您的异常处理程序中,所以如果您想要从那里打印,您必须执行基于 C FILE* 的输出之类的操作。

    【讨论】:

    • @Jerry,根据 C++ 标准(哇,我喜欢这样说)27.4 第 2 小节,静态对象构造函数和析构函数可以访问 iostream 全局对象。
    • @MSN:(实际上是 §27.**3**/2)。出于某种原因,我以前从未注意到那个脚注。显然意图是存在的,但我没有看到任何规范性语言可以真正保证这一点。无论如何,您的观点可能是正确的:我上面的代码过于偏执。
    【解决方案4】:

    您可以将函数包装在另一个函数中,该函数捕获异常并提醒用户问题(或创建具有安全默认值的键)

    【讨论】:

      【解决方案5】:

      您可以创建一个延迟构造对象的包装类。然后,当它第一次使用时,如果构造函数抛出,它将抛出它第一次使用的地方。

      这样做的好处是在调用 main() 之前不会运行大量代码,并且如果您从未实际使用过全局对象,它将永远不会被初始化。

      代码:

      #include <boost/bind.hpp>
      #include <boost/function.hpp>
      #include <boost/scoped_ptr.hpp>
      #include <boost/thread/once.hpp>
      #include <iostream>
      
      const boost::once_flag DEFAULT_ONCE_FLAG = BOOST_ONCE_INIT;
      template <typename T>
      class DelayedConstruction {
        public:
        DelayedConstruction(boost::function<T* (void) > const & init = &DelayedConstruction::default_initializer ) :
          m_initializer(init), m_flag(DEFAULT_ONCE_FLAG) { }
      
        T const & operator*() const { 
          boost::call_once(m_flag, boost::bind(&DelayedConstruction::initialize, this) ) ;
          if ( ! m_object )
            throw std::runtime_error("Object could not be initialized") ;
          return *m_object ;
        }
        T const * operator->() const {
          boost::call_once(m_flag, boost::bind(&DelayedConstruction::initialize, this) ) ;
          if ( ! m_object )
            throw std::runtime_error("Object could not be initialized") ;
          return m_object.get() ;
        }
        static T* default_initializer() { return new T; }
        private:
        void initialize() const {
          m_object.reset( m_initializer() ) ;
        }
        boost::function<T* (void) > m_initializer ;
        mutable boost::scoped_ptr<T> m_object ;
        mutable boost::once_flag m_flag ; 
      };
      
      struct Foo {
        Foo(int x = 0) : m_x(x) {
          if ( x == 1 ) throw std::runtime_error("Can't be 1") ;
        }
        int m_x ;
      } ;
      
      Foo* make_custom_foo() {
        return new Foo(1) ;
      }
      
      DelayedConstruction< const Foo > g_myFoo ;
      DelayedConstruction< const Foo > g_anotherFoo(&::make_custom_foo) ;
      
      int main() {
      
        try {
          std::cout << "My Foo: " << g_myFoo->m_x << std::endl ;
          std::cout << "Another Foo: " << g_anotherFoo->m_x << std::endl ;
        } catch ( std::runtime_error const & e ) {
          std::cout << "ERROR: " << e.what() << std::endl ;
        }
      
        return 0 ;
      }
      

      打印出来:

      My Foo: 0
      ERROR: Can't be 1
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-19
        相关资源
        最近更新 更多