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

时间:2009-12-23 06:53:54

标签: c++ exception constructor

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

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

bool MyClass::Create(...);

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

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

任何人都可以帮助我吗?提前谢谢。

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

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 个答案:

答案 0 :(得分:7)

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

  1. 构造函数接受一个参数/指针,该参数将错误状态传递给调用者。
  2. 该类实现一个方法,该方法将返回构造函数的错误状态。调用者将负责检查此方法。
  3. 如果您使用这些技术中的任何一种,请确保您的析构函数可以处理构造函数失败的实例。

答案 1 :(得分:4)

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

如果您的公司要求您禁用例外,那么您还需要一个公司范围(或至少是项目范围的)惯例来替换它。你需要为所有(非平凡的)函数定义一个类型,并在任何地方使用它 - 否则你将获得一个不可维护的布尔波特和不兼容的枚举传递并在每个级别手动转换。 / p>

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

答案 2 :(得分:3)

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

  1. 在构造函数中执行某种assert()以暂停执行;不可忽视,但无法从中恢复。
  2. 在Init功能中进行实际构建。
  3. ...或将其保留在构造函数中,但设置“坏”标记。
  4. 就我个人而言,我认为2严格优于3,因为它不会增加类的大小,并且在未调用“check”函数时使其更加明显。我听到引用的原因就像你可以访问虚拟功能一样,但我一直认为它相当弱。

答案 3 :(得分:3)

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

这很难看,我真的不推荐它,如果你不允许引入构造函数,你可能会有一个哑构造函数和一个init函数:

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

Foo obj;

答案 6 :(得分:2)

我已经尝试了所有可以找到的异常的替代方法(成员错误变量,甚至是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类)
  • 线程安全
  • 类中的零大小开销
  • Error类可以100%不透明;使用宏来访问,声明和传递它,它可以包括各种信息,包括但不限于源文件和行,函数名,堆栈回溯等。
  • 它适用于非常复杂的表达式,在很多情况下几乎就像例外一样

答案 7 :(得分:0)

两个建议:

  • 老式的setjmp / longjmp()
  • 全球错误的变量

答案 8 :(得分:0)

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;         
}

答案 9 :(得分:0)

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

我不喜欢通过输出参数检索错误,因此我将成功实例和可能的实例都放在了结构中:

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++