Как я могу проверить сбой в конструкторе () без использования исключений? - PullRequest
4 голосов
/ 23 декабря 2009

Все классы, над которыми я работаю, имеют методы 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;

Ответы [ 10 ]

7 голосов
/ 23 декабря 2009

Если вы не хотите использовать исключения, есть два способа сообщить вызывающей стороне, был ли конструктор успешным или нет:

  1. Конструктор получает ссылку / указатель на параметр, который сообщит вызывающей стороне статус ошибки.
  2. В классе реализован метод, который будет возвращать состояние ошибки конструктора. Звонящий будет отвечать за проверку этого метода.

Если вы используете любой из этих методов, убедитесь, что ваш деструктор может обработать экземпляр, в котором конструктор потерпел неудачу.

4 голосов
/ 23 декабря 2009

C ++ без исключений, по сути, является языком, совершенно отличным от C ++, в котором многие из идиом, которые придают C ++ уникальную выразительную силу, оказываются бессильными. Как вы указали, конструкторы лишены своей полезности, и вся нетривиальная инициализация должна быть перемещена в псевдоконструктор второго уровня, который может возвращать индикацию ошибки. (Некоторые люди также защищают соответствующий псевдодеструктор из-за ошибочного чувства симметрии, но это совершенно бессмысленно). В качестве альтернативы конструктор может установить «сконструированный» флаг в случае успеха, и у каждого класса может быть «сконструированный» метод, который проверяет этот и все его дочерние элементы.

Если ваша компания обязывает вас отключить исключения, то вам также понадобится соглашение всей компании (или, по крайней мере, проекта), чтобы заменить его. Вам нужно будет определить тип для всех (нетривиальных) функций, которые будут возвращаться, и использовать его последовательно везде - в противном случае вы получите неприемлемую "кучку" логических значений и несовместимых перечислений, которые будут передаваться и преобразовываться вручную на каждом уровне.

В этом случае Foo также потребуется метод Initialize, который вызывает a.Initialize и выдает ошибку в случае сбоя.

3 голосов
/ 19 декабря 2012

Безопасно избегать кода, вызывающего сбои в конструкторе или дескрипторе. Пусть еще один член скажет, bool Initialize(), bool Uninitialize(), чтобы иметь такие коды.

3 голосов
/ 23 декабря 2009

Нет хорошего пути; это одна из основных причин, по которой они были добавлены в язык. Без исключений либо:

  1. Выполнить своего рода assert () в конструкторе, чтобы остановить выполнение; невозможно игнорировать, но невозможно восстановить после.
  2. Выполните свою фактическую конструкцию в функции Init.
  3. ... или оставьте его в конструкторе, но установите флаг "bad".

Лично я думаю, что 2 строго лучше, чем 3, так как он не увеличивает размер класса и делает его более заметным, когда не вызывается функция "check". Я слышу процитированные причины, например, что вы можете получить доступ к виртуальным функциям, но я всегда считал, что это довольно слабая ситуация.

2 голосов
/ 27 июля 2010

Я перепробовал все альтернативы исключениям, которые мог найти (переменная ошибки члена, даже 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% непрозрачным; с помощью макросов для доступа, объявления и передачи он может включать в себя все виды информации, включая, но не ограничиваясь, исходный файл и строку, имя функции, обратный след стека и т. д.
  • это относится к довольно сложным выражениям, во многих случаях делая его почти как исключения
2 голосов
/ 23 декабря 2009

Это ужасно, и я не очень рекомендую это, , но , если вам не разрешено добавлять конструктор, вы можете иметь тупой конструктор и функцию инициализации:

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

Foo obj;
2 голосов
/ 23 декабря 2009

почему исключения не должны использоваться? Лучший способ вернуть ошибку из конструктора - создать исключение. Неправильно созданный объект не должен использоваться, и исключение гарантирует, что.

Вы можете обратиться к этому FAQ: Как я могу обработать конструктор, который выходит из строя?

0 голосов
/ 24 февраля 2019

Я тоже сталкивался с этой проблемой. Я думаю, что элегантное решение - сделать реальный конструктор приватным, а затем использовать фабрику для возврата экземпляра и ошибки.

Я не фанат извлечения ошибки через выходной аргумент, поэтому я бы поместил в структуру как успешный, так и возможный экземпляр:

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.

Рекомендую посмотреть выступление Андрея Александреску о систематической обработке ошибок в C ++ .

0 голосов
/ 23 декабря 2009
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;         
}
0 голосов
/ 23 декабря 2009

два предложения:

  • старомодный setjmp / longjmp ()
  • глобальная переменная типа errno
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...