2016-08-02 2 views
8

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

По существу, я ищу на C++ эквивалент следующего Java идиомы:

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

Я не хочу, чтобы перехватывать исключения из x.doSomething(), только конструктор.

Я предполагаю, что я ищу способ отделить декларацию и инициализацию x.

Можно ли выполнить это без использования распределений кучи и указателей?

+0

Да. Поместите все на успешный путь внутри блока try. Вы останетесь в области функций. – StoryTeller

+0

@StoryTeller, если я не хочу перехватывать исключения из, например, 'x.doSomething()', только исключения из конструктора? –

+0

@AndrewSun Используйте разные исключения или поместите 'x.doSomething()' во внутренний блок исключений. – Holt

ответ

11

Вы можете использовать std::optional от C++ 17:

bool foo() { 
    std::optional<Bar> x; // x contains nothing; no Bar constructed 
    try { 
     x.emplace();  // construct Bar (via default constructor) 
    } catch (const Exception& e) { 
     return false; 
    } 
    x->doSomething();  // call Bar::doSomething() via x (also (*x).doSomething()) 
    return true; 
} 
+0

@ Jarod42 Я колебался, как написать это для defualt ctor 'Bar'. 'X.emplace()'? 'X.emplace ({})'? – songyuanyao

+1

Это 'x.emplace()' для конструктора по умолчанию. Позднее не будет компилироваться. – Jarod42

+0

@ Jarod42 Спасибо! – songyuanyao

2

Вы должны выбрать между вариантом

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = std::make_unique<Bar>(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 

или

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    return true; 
} 
2

Обычно, если вы хотите, чтобы избежать распределения кучи, вы не можете отделить декларацию локальной переменной из ее определения. Так что если вы должны были объединить все в одной функции, вы должны сделать окружать весь объем x с try/catch блока:

boolean foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 
5

Да, это возможно, если вы поставите весь код в пункте try, для например, с помощью function try block (чтобы избежать ненужной вложенности и области видимости):

bool foo() try 
{ 
    Bar x; 
    x.doSomething(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

Или в try пункте вызове другой функции, которая делает реальную работу:

void real_foo() 
{ 
    Bar x; 
    x.doSomething(); 
} 

bool foo() try 
{ 
    real_foo(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

Обратите внимание, что часто не рекомендуется создавать исключения в конструкторе, поскольку это остановит конструкцию объекта, и его деструктор не будет вызван.


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

  1. Простой и стандартный способ: использовать указатели.

  2. Используйте двухэтапную конструкцию: используйте конструктор по умолчанию, который не может генерировать исключения, а затем вызывает специальную функцию «построить», которая может генерировать исключения.

Второй способ был распространен прежде, чем C++ был стандартизован, и широко используется в коде для Symbian system. Это не часто встречается, так как использование указателей для этого намного проще и проще, особенно сегодня с хорошими интеллектуальными указателями. Я действительно не рекомендую второй способ в современном C++.

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

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

+0

Функция try block не нужна. Регулярного 'try catch' достаточно. – Jarod42

+0

@ Jarod42, тогда 'Bar' должен находиться в другом блоке, а не в блоке уровня функции. – Ajay

+1

Это не будет имитировать поведение данного java-кода, поскольку вы будете ловить исключения из 'x.doSomething()'. – Holt

2

No. Из вашего примера Java, вам придется выбирать между этими 2 возможностями:

Без указателей:

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 

с указателями:

bool foo() { 
    Bar* x = nullptr; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 

    delete x; // don't forget to free memory 

    return true; 
} 

Или с помощью управляемых указателей:

#include <memory> 

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = new Bar();    // until C++14 
     x = std::make_unique<Bar>(); // since C++14 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 
+3

Возможно, вы захотите добавить «delete x» где-то;) – CompuChip

+0

Обычно я использую управляемые указатели. Мой плохой ^^ – wasthishelpful

+1

Да. Недостатком второго примера, как у вас сейчас, является то, что вы действительно хотите поймать какие-либо исключения из 'x-> doSomething', чтобы убедиться, что' delete' будет выполнен, но тогда вам понадобится отдельный блок try-catch с rethrow - потенциальные головные боли, которые 'unique_ptr' прекрасно решает. Хорошо, что вы его добавили. – CompuChip

5

Эта идиома Java не хорошо переводит на C++, так как Bar x; потребует конструктор по умолчанию, даже если вашему реальному конструктору нужны аргументы.

Я бы посоветовал бороться с языком в такой степени - расширение try блока достаточно, - но если вы действительно хотите сузить, то вы могли использовать функцию и полагаться на оптимизации возвращаемого значения чтобы избежать Копия значения:

Bar foobar() 
{ 
    try { 
     return Bar(); 
    } catch (Exception& e){ 
     /* Do something here like throwing a specific construction exception 
      which you intercept at the call site.*/ 
    } 
} 

Но на самом деле вы можете создать конкретное исключение при построении, поэтому полностью исключить этот функциональный подход.

+1

RVO & move semantics делают это лучшим решением, на мой взгляд. – StoryTeller

+0

До C++ 17 OP также должен обрабатывать исключение экземпляра copy/move. – Jarod42

+0

@ Jarod42, что сделал C++ 17 изменение? (Просто прошу ради моего собственного любопытства). – StoryTeller

1

В пересмотренном вопросе OP добавляет требование

Я не хочу, чтобы перехватывать исключения из x.doSomething(), только конструктор [локальной переменной].

Простой способ перевести код Java

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

& hellip; на C++, затем использовать Optional_ класс (как Barton-Nackmann Fallible, boost::optional или 17 std::optional C++)

auto foo() 
    -> bool 
{ 
    Optional_<Bar> xo; 
    try 
    { 
     xo.emplace(); 
    } 
    catch(...) 
    { 
     return false; 
    } 

    Bar& x = *xo; 
    // Possibly other code here, then: 
    x.doSomething(); 
    return true; 
} 

Хорошая альтернатива реорганизовать этот код, как это:

struct Something_failure {}; 

void do_something(Bar& o) 
{ 
    // Possibly other code here, then: 
    o.doSomething(); 
} 

auto foo() 
    -> bool 
{ 
    try 
    { 
     Bar x; 

     do_something(x); 
     return true; 
    } 
    catch(Something_failure const&) 
    { 
     throw; 
    } 
    catch(...) 
    {} 
    return false; 
} 

Если вам не нравятся вышеупомянутые подходы, вы можете всегда использовать динамически выделенный Bar экземпляр, например используя std::unique_ptr для гарантированной очистки, который, однако, имеет общие накладные расходы на динамическое распределение. В Java каждый объект динамически распределяется так, чтобы он не представлял собой серьезный недостаток. Но в C++ большинство объектов являются супербыстрое распределением стека, так что динамическое распределение - это довольно медленная операция по сравнению с обычными операциями, поэтому возможная концептуальная простота динамического распределения должна быть взвешена против этого.

Смежные вопросы