обработка исключений в списке инициализатора конструктора - PullRequest
4 голосов
/ 30 марта 2009

В моем проекте я нашел фрагмент кода, в котором вызывался метод в списке инициализатора конструктора.

Test2(Test* pTest):m_pTest(pTest), m_nDuplicateID(pTest->getTestID())
    {
    }

Я заметил, что есть вероятность, что пользователи Test2 могут передать NULL конструктору. Поскольку указатель используется без проверки, существует вероятность нарушения прав доступа.

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

//Test class stores the unique ID and returns the same with API getTestID
class Test
{
public:

    Test(int nID):m_nID(nID){
    }

    int getTestID() const
    {
            return m_nID;
    }
private:
    int m_nID;

};


class Test2
{
public:

    Test2(Test* pTest) 
        try :m_pTest(pTest), m_nDuplicateID(pTest->getTestID())
    {
    }
    catch (...) 
    {
        cout<<"exception cought "<< endl;
    }

    void printDupID()
    {
        cout<<"Duplicate ID" << m_nDuplicateID << endl;
    }
private:

    Test* m_pTest;
    int m_nDuplicateID;
};

int main(int argc, char* argv[])
{

    Test* pTest = new Test(10);


    Test2 aTest2(pTest);
    aTest2.printDupID();


    delete pTest;

    return 0;
}

Этот код не компилируется в VC6.0 . Нужно ли вносить какие-либо изменения для его компиляции в VC 6.0?

Кроме того, в одной из статей я обнаружил, что использование try в списке инициализатора конструктора строго не соответствует стандартам C ++. В таком случае, как мы обрабатываем исключения в списке инициализаторов конструктора (стандартный способ обработки)?

Спасибо.

Ответы [ 7 ]

11 голосов
/ 30 марта 2009

C ++ Стандартный раздел 15/3

Функциональный блок try связывает handler-seqwith thector-initializer, если присутствует и функция-тело. исключение выдается во время исполнения выражений инициализатора в ctor-инициализатор или во время выполнение функции-тела передает управление обработчику в function-try-block так же, как исключение, выброшенное во время выполнение трансферных блоков управление другими обработчиками.

class C 
{  
    int i;  
    double d;  
public:  
    C(int, double);  
};  

C::C(int ii, double id)  
try  : i(f(ii)), d(id)  
{  
//constructor function body  
}  catch (...)  
{  
//handles exceptions thrown from the ctor-initializer  
//and from the constructor functionbody  
}
8 голосов
/ 30 марта 2009

Во-первых, если вы разыменуете указатель NULL, стандарт C ++ не гарантирует, что будет выдано исключение, поэтому ваш код для этого случая бесполезен.

Во-вторых, если возникнет исключение, что сделает ваш обработчик исключений?

В-третьих, блоки исключений конструктора / функции, как широко полагают, являются лишними времени - взгляните на этот http://www.gotw.ca/gotw/066.htm и другие статьи на сайте GotW Херба Саттера.

4 голосов
/ 30 марта 2009

Согласно этой статье , похоже, что в VC ++ 6.0

это просто невозможно.

Вы должны либо перейти на 7.0, либо просто выполнить инициализацию в теле конструктора.

3 голосов
/ 09 апреля 2009

Разве вы не можете просто использовать функцию для проверки ptr, например ::100100

template<typename P>
P* checkPtr (P* p)
{
    if (p == 0)
        throw std::runtime_error ("Null pointer");
    return p;
}

class Test2
{
public:
    Test2 (Test* pTest)
        : m_pTest (checkPtr (pTest))
    {
    }

    Test* m_pTest;
};
1 голос
/ 30 марта 2009

Люди все еще используют VC6? Серьезно, VC6 вряд ли является стандартным компилятором жалоб. Сделайте себе одолжение и, по крайней мере, получите VS2005. VC6 это твоя проблема. Попробуйте VS2008 Express и посмотрите, скомпилируется ли он.

Другой вариант, конечно, это взять ссылку на конструкцию, которую нужно связать.

0 голосов
/ 29 мая 2015

Уже есть много полезных ответов, но я постараюсь добавить немного, может быть, это кому-нибудь поможет.

Прежде всего, как уже упоминалось, разыменование nullptr или неверного указателя (адреса) не вызывает исключения в стандарте C ++. MSVC поддерживает его с помощью Структурной обработки исключений , но он не переносим. Подробнее об этом читайте в этом ответе .

Блок try функции в конструкторе не позволяет вам подавить исключение, он будет распространяться в любом случае, если вы не выбросите другое. И когда вы вводите предложение catch, все члены класса уже уничтожены. Таким образом, единственное правдоподобное, что вы можете сделать в нем, это как-то зарегистрировать ошибку или, возможно, изменить некоторые глобальные переменные. Вот почему он считается более или менее бесполезным.

Что касается вашего исходного кода

Test2(Test* pTest):m_pTest(pTest), m_nDuplicateID(pTest->getTestID())
    {
    }

вы можете использовать троичный оператор для проверки нулевой pTest только в списке инициализаторов и делать некоторые соответствующие действия в случае, если он нулевой - просто установите m_nDuplicateID в nullptr или какое-либо другое значение в зависимости от его типа, вызовите другую функцию и используйте ее тип возврата и т. д .:

Test2(Test* pTest):
    m_pTest(pTest),
    m_nDuplicateID( pTest ? pTest->getTestID() : /*some value or call*/ )
{
}

вы могли бы даже использовать несколько вложенных тернарных операторов для создания более сложных потоков выполнения.

И просто для полноты, это не относится к вашему коду, но он может утомить кого-то в той же ситуации. Если вы использовали член своего класса m_pTest для инициализации m_nDuplicateID, это будет зависеть от порядка этих членов в объявлении класса, потому что члены класса в списке инициализатора инициализируются в порядке объявления, а не в том порядке, в котором они появляются в самом списке инициализатора. , так что это может быть проблемой, и лучше избегать зависимостей порядка инициализации членов:

class A
{
    A( B* pTest );
    int m_nDuplicateID;
    B* m_pTest;
};

A::A( B* pTest ) :
    m_pTest( pTest ),
    m_nDuplicateID( m_pTest->someMethod() ) // here m_pTest isn't initialized yet,
                                            // so access violation probably
{
}
0 голосов
/ 27 марта 2013

(для коллег по Google)

Другое решение, если мы не хотим хранить копию ptr / shared_ptr:

class Foo::Pimpl
{
public:
    bool paramTest_;

    Pimpl(ConstNodePtr root)
    try  :
       paramTest_( root ? true : throw std::invalid_argument("Foo (pimpl) constructed from NULL node")),
    ...
{
    ...
}  catch (...)
{
    throw; // rethrow
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...