【问题标题】:How can I check the failure in constructor() without using exceptions?如何在不使用异常的情况下检查构造函数()中的失败?
【发布时间】:2010-12-29 09:29:42
【问题描述】:

我正在处理的所有类都有 Create()/Destroy()(或 Initialize()/Finalized())方法。

Create() 方法的返回值是 bool,如下所示。

bool MyClass::Create(...);

所以我可以通过返回值来检查实例的初始化是否成功。

没有 Create()/Destroy() 我可以在 constructor() 和 destructor() 中做同样的工作,但我无法解决下面的问题。

谁能帮帮我?提前致谢。

我不能使用例外,因为我的公司不喜欢它。

class Foo
{
private:
    AnotherClass a;
public:
    Foo()
    {
        if(a.Initialize() == false)
        {
            //???
            //Can I notify the failure to the user of this class without using exception?
        }
    }
    ...
};

Foo obj;

【问题讨论】:

  • 您的公司是否意识到每次调用new 或使用标准库时都会出现异常?
  • @GMan 我们倾向于覆盖 operator new。我不知道图书馆的问题,你能告诉我更多吗?
  • 在这种情况下使用 nothrow 关键字来避免 . int *p = new (nothrow) int [100]; //on fail 返回 NULL 指针但是你将如何避免 bad_cast 异常,可能你必须关闭异常?
  • 我工作的一家商店有一条规则,即构造函数不能失败。所以构造函数的代码很少,用户不得不调用一个可能会失败的初始化方法。我的理解是消除异常的使用。 new 的库代码已修改为不抛出异常。

标签: c++ exception constructor


【解决方案1】:

如果不想使用异常,有两种方法可以让调用者知道构造函数是否成功:

  1. 构造函数接受一个参数的引用/指针,该参数将向调用者传达错误状态。
  2. 该类实现了一个方法,该方法将返回构造函数的错误状态。调用者将负责检查此方法。

如果您使用这些技术中的任何一种,请确保您的析构函数可以处理构造函数失败的实例。

【讨论】:

    【解决方案2】:

    毫无例外的 C++ 本质上是一种与 C++ 完全不同的语言,在 C++ 中,许多赋予 C++ 独特表达能力的习语都变得无能为力。正如您所指出的,构造函数被剥夺了它们的用处,并且所有非平凡的初始化都必须移到可以返回错误指示的第二阶段伪构造函数中。 (有些人还出于被误导的对称感而提倡匹配的伪析构函数,但这完全没有意义)。或者,构造函数可以在成功时设置一个“已构造”标志,并且每个类都可以有一个“已构造”方法来检查它及其所有子类。

    如果您的公司要求您禁用例外,那么您还需要一个公司范围(或至少是项目范围)的约定来替换它。您需要为要返回的所有(非平凡)函数定义一个类型,并在任何地方始终如一地使用它 - 否则您将得到一个无法维护的大杂烩,布尔值和不兼容的枚举被传递并在每个级别手动转换。

    在这种情况下,Foo 还需要一个 Initialize 方法,该方法调用 a.Initialize 并在失败时退出。

    【讨论】:

      【解决方案3】:

      没有好办法;这是它们首先被添加到语言中的主要原因之一。无一例外:

      1. 在构造函数中执行某种 assert() 以停止执行;无法忽视,但无法恢复。
      2. 在 Init 函数中进行实际构造。
      3. ... 或将其保留在构造函数中,但设置一个“坏”标志。

      就我个人而言,我认为 2 严格来说比 3 好,因为它不会增加班级规模,并且在不调用“检查”函数时使其更明显。我听说有一些原因,比如你可以访问虚函数,但我一直认为这是相当弱的。

      【讨论】:

      • 但是第(2)点和第(3)点本质上不一样吗?你们都需要一个“坏”标志,以便后续方法调用可以检查。我会在第 (2) 点争辩说,您正在最大限度地减少用户忘记调用“init()”函数的机会。
      【解决方案4】:

      避免在构造函数或析构函数中导致失败的代码是安全的。让另外一位成员说,bool Initialize()bool Uninitialize() 拥有这些代码。

      【讨论】:

        【解决方案5】:

        我也遇到过这个问题。我认为一个优雅的解决方案是将真正的构造函数设为私有,然后使用工厂返回实例和错误。

        我不喜欢通过输出参数检索错误,所以我会将成功和可能的实例都放在一个结构中:

        template < class T >
        struct expect {
           expect( T v ) :
              failure(false),
              value(v) // or std::move(v)
           {
           }
        
           expect( int e ) :
              failure(true),
              error(e)
           {
           }
        
           bool failure;
           union {
              T value;
              int error;
           };
        };
        
        class A {
           public:
              expect<A> create( /* arguments */ ) {
                 if( /* check fails */ ) {
                    return expect(error_code);
                 }
                 return expect( A(/*arguments*/) );
              }
           private:
              A( /* constructor that does not fail */ );
        };
        

        这是一种流行的模式,被提议作为标准的扩展。这样做的好处是您的代码仍然可以大量使用 RAII。

        我推荐观看 Andrei Alexandrescu 的 talk on systematic error handling in C++

        【讨论】:

          【解决方案6】:

          为什么不应该使用异常?从构造函数返回错误的最好方法是抛出异常。构造错误的对象不应该使用,并且抛出异常可以确保。

          您可以参考这个常见问题解答:How can I handle a constructor that fails?

          【讨论】:

          • sevity 写道:公司政策不允许例外。
          【解决方案7】:

          这是丑陋的,我不推荐它,但是如果你不允许在构造函数中抛出你可以有一个愚蠢的构造函数和一个 init 函数:

          class Foo
          {
          private:
              AnotherClass a;
          public:
              Foo(){};
              bool initialize()
              {
                  return a.Initialize();
              }
              ...
          };
          
          Foo obj;
          

          【讨论】:

          • 这与我描述为 Create()/Destroy() 的方式相同。
          • 这可能是唯一可行的方法。问题是构造函数可以隐式运行。当它们隐式运行时,您无法检查状态。
          【解决方案8】:

          我已经尝试了所有我能找到的异常的替代方案(成员错误变量,甚至是 setjmp/longjmp),但它们都以自己的特殊方式吸吮。我喜欢的一种非常罕见的模式是传递对错误对象的引用,并检查是否有错误未决作为任何函数中的第一个操作:

          int function1(Error& e, char * arg)
          {
              if(e.failure())
                  return -1; // a legal, but invalid value
          
              // ...
          }
          
          int function2(Error& e, int arg)
          {
              if(e.failure())
                  return -1; // a legal, but invalid value
          
              // ...
          }
          
          int function3(Error& e, char * arg)
          {
              if(e.failure())
                  return -1;
          
              // if function1 fails:
              //  * function2 will ignore the invalid value returned
              //  * the error will cascade, making function2 "fail" as well
              //  * the error will cascade, making function3 "fail" as well
              return function2(e, function1(e, arg));
          }
          

          通过一些工作,它也适用于构造函数:

          class Base1
          {
          protected:
              Base1(Error& e)
              {
                  if(e.failure())
                      return;
          
                  // ...
              }
          
          // ...
          };
          
          class Base2
          {
          protected:
              Base2(Error& e)
              {
                  if(e.failure())
                      return;
          
                  // ...
              }
          
          // ...
          };
          
          class Derived: public Base1, public Base2
          {
          public:
              Derived(Error& e): Base1(e), Base2(e)
              {
                  if(e.failure())
                      return;
          
                  ...
              }
          };
          

          主要问题是如果动态分配对象的构造函数失败,您不会自动删除。我通常将 new 的调用包装在这样的函数中:

          // yes, of course we need to wrap operator new too
          void * operator new(Error& e, size_t n)
          {
              if(e.failure())
                  return NULL;
          
              void * p = ::operator new(n, std::nothrow_t());
          
              if(p == NULL)
                  /* set e to "out of memory" error */;
          
              return p;
          }
          
          template<class T> T * guard_new(Error& e, T * p)
          {
              if(e.failure())
              {
                  delete p;
                  return NULL;
              }
          
              return p;
          }
          

          会这样使用:

          Derived o = guard_new(e, new(e) Derived(e));
          

          这种技术的优点包括:

          • 与 C 的互操作性(如果正确声明了 Error 类)
          • 线程安全
          • 类中的零大小开销
          • 错误类可以是 100% 不透明的;使用宏来访问、声明和传递它,它可以包含各种信息,包括但不限于源文件和行、函数名、堆栈回溯等。
          • 它适用于相当复杂的表达式,在许多情况下几乎类似于异常

          【讨论】:

          • 顺便说一句,我不相信这种技术。我认为 IBM 发明了它,因为我唯一见过它的地方是他们的 ICU 图书馆
          【解决方案9】:

          两个建议:

          • 老式的 setjmp/longjmp()
          • 一个全局 errno 类似变量

          【讨论】:

            【解决方案10】:
            class Foo
            {
            private:
                AnotherClass a;
                bool m_bInitFail; //store the status of initialization failure
            
            public:
                Foo(bool bInitFail = false) : m_bInitFail(bInitFail)
                {
                    m_bInitFail  = a.Initialize();           
                }
            
                bool GetInitStatus () { return m_bInitFail ; }
            };
            
            int main()
            {
              Foo fobj;
              bool bStatus = fobj.GetInitStatus();
              return 0;         
            }
            

            【讨论】:

              猜你喜欢
              • 2017-03-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-04
              • 1970-01-01
              • 2010-11-16
              • 2015-10-19
              相关资源
              最近更新 更多