代码之家  ›  专栏  ›  技术社区  ›  CodingLab

如何在不使用异常的情况下检查constructor()中的失败?

  •  3
  • CodingLab  · 技术社区  · 15 年前

    我正在处理的所有类都有create()/destroy()(或initialize()/finished())方法。

    create()方法的返回值为 布尔 像下面一样。

    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;
    
    10 回复  |  直到 5 年前
        1
  •  7
  •   R Samuel Klatchko    15 年前

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

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

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

        2
  •  4
  •   Mike Seymour    15 年前

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

    如果您的公司要求您禁用异常,那么您还需要一个公司范围(或者至少是项目范围)的约定来替换它。您需要为所有要返回的(非琐碎的)函数定义一个类型,并且在任何地方都一致地使用它——否则,您将得到一个不可维护的Boolean和不兼容的枚举的大杂烩,并在每个级别上进行传递和手动转换。

    在这种情况下, Foo 也需要一个 Initialize 方法,调用 a.Initialize 如果失败的话,就用饵诱捕。

        3
  •  3
  •   Todd Gardner    15 年前

    没有一个好的方法;这是他们最初被添加到语言中的主要原因之一。无例外:

    1. 在构造函数中执行某种类型的assert()以停止执行;不能忽略,但无法从中恢复。
    2. 在init函数中进行实际构造。
    3. ……或者将其保留在构造函数中,但设置一个“坏”标志。

    就我个人而言,我认为2严格地比3好,因为它不会增加类的大小,并且在不调用“check”函数时更容易看到它。我听到引用的原因有很多,比如您可以访问虚拟函数,但我一直认为这相当弱。

        4
  •  3
  •   PaulStock    12 年前

    避免在构造函数或说明中使用导致失败的代码是安全的。再让一个成员说, bool Initialize() , bool Uninitialize() 拥有这些代码。

        5
  •  2
  •   aJ.    15 年前

    为什么不应使用例外?从构造函数返回错误的最佳方法是抛出异常。错误构造的对象不应该使用,抛出异常可以确保这一点。

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

        6
  •  2
  •   mikelong    15 年前

    这很难看,我并不推荐, 但是 如果不允许抛出构造函数,则可以有一个哑构造函数和一个init函数:

    class Foo
    {
    private:
        AnotherClass a;
    public:
        Foo(){};
        bool initialize()
        {
            return a.Initialize();
        }
        ...
    };
    
    Foo obj;
    
        7
  •  2
  •   KJKHyperion    14 年前

    我尝试了所有我能找到的异常的替代方法(成员错误变量,甚至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的互操作性(如果错误类被适当声明)
    • 线程安全
    • 类中的零大小开销
    • 错误类可以是100%不透明的;通过使用宏来访问、声明和传递它,它可以包含各种信息,包括但不限于源文件和行、函数名、堆栈回溯等。
    • 它适用于非常复杂的表达式,在许多情况下,它几乎与异常相似。
        8
  •  0
  •   anon    15 年前

    两个建议:

    • 老式的setjmp/longjmp()。
    • 一个类似于errno的全局变量
        9
  •  0
  •   Ashish    15 年前
    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;         
    }
    
        10
  •  0
  •   Jorge Bellon    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。

    我建议你看安德烈·亚历山大的 talk on systematic error handling in C++ .