Наконец в C ++ - PullRequest
       48

Наконец в C ++

10 голосов
/ 24 декабря 2008

Является ли это хорошим способом для реализации окончательного поведения в стандартном C ++? (Без специальных указателей)

class Exception : public Exception
    { public: virtual bool isException() { return true; } };

class NoException : public Exception
    { public: bool isException() { return false; } };


Object *myObject = 0;

try
{
  // OBJECT CREATION AND PROCESSING
  try
  {
    myObject = new Object();

    // Do something with myObject.
  }

  // EXCEPTION HANDLING
  catch (Exception &e)
  {
    // When there is an excepion, handle or throw,
    // else NoException will be thrown.
  }

  throw NoException();
}

// CLEAN UP
catch (Exception &e)
{
  delete myObject;

  if (e.isException()) throw e;
}
  1. Нет исключений, выданных объектом -> NoException -> Объект очищен
  2. Исключение, сгенерированное объектом -> Обработано -> NoException -> Объект очищен
  3. Исключение, выброшенное объектом -> Брошенный -> Исключение -> Объект очищен -> Брошенный

Ответы [ 6 ]

28 голосов
/ 24 декабря 2008

Стандартный ответ заключается в использовании некоторого варианта resource-alloc-is-initialization сокращенного RAII. По сути, вы создаете переменную, которая имеет ту же область видимости, что и блок, который будет внутри блока перед элементом finally, затем выполняете работу в блоке finally внутри деструктора объектов.

try {
   // Some work
}
finally {
   // Cleanup code
}

становится

class Cleanup
{
public:
    ~Cleanup()
    {
        // Cleanup code
    }
}

Cleanup cleanupObj;

// Some work.

Это выглядит ужасно неудобно, но обычно есть уже существующий объект, который сделает уборку за вас. В вашем случае, похоже, что вы хотите уничтожить объект в блоке finally, что означает, что умный или уникальный указатель будет делать то, что вы хотите:

std::unique_ptr<Object> obj(new Object());

или современный C ++

auto obj = std::make_unique<Object>();

Независимо от того, какие исключения выброшены, объект будет уничтожен. Возвращаясь к RAII, в этом случае распределение ресурсов выделяет память для объекта и создает его, а инициализация - инициализация unique_ptr.

11 голосов
/ 24 декабря 2008

Нет. Стандартный способ создания окончательно подобного способа состоит в том, чтобы разделить задачи (http://en.wikipedia.org/wiki/Separation_of_concerns)) и сделать объекты, которые используются в блоке try, автоматически высвобождают ресурсы в их деструкторе (называемом «Scope Bound Resource Management»). Так как деструкторы работают детерминистически, в отличие от Java, вы можете положиться на них для безопасной очистки. Таким образом, объекты, которые заполучили ресурс, также очистят ресурс.

Одним из особых способов является динамическое распределение памяти. Так как вы - тот, кто получает ресурс, вам придется снова убирать. Здесь можно использовать умные указатели.

try {
    // auto_ptr will release the memory safely upon an exception or normal 
    // flow out of the block. Notice we use the "const auto_ptr idiom".
    // http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
    std::auto_ptr<A> const aptr(new A);
} 
// catch...
4 голосов
/ 24 декабря 2008

Если по какой-то странной причине у вас нет доступа к стандартным библиотекам, то очень легко реализовать столько, сколько вам нужно, типа интеллектуального указателя для управления ресурсом. Это может выглядеть немного многословно, но это меньше кода, чем эти вложенные блоки try / catch, и вам нужно определить этот шаблон только один раз, а не один раз для ресурса, который требует управления:

template<typename T>
struct MyDeletable {
    explicit MyDeletable(T *ptr) : ptr_(ptr) { }
    ~MyDeleteable() { delete ptr_; }
private:
    T *ptr_;
    MyDeletable(const MyDeletable &);
    MyDeletable &operator=(const MyDeletable &);
};

void myfunction() {
    // it's generally recommended that these two be done on one line.
    // But it's possible to overdo that, and accidentally write
    // exception-unsafe code if there are multiple parameters involved.
    // So by all means make it a one-liner, but never forget that there are
    // two distinct steps, and the second one must be nothrow.
    Object *myObject = new Object();
    MyDeletable<Object> deleter(myObject);

    // do something with my object

    return;
}

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

Метод try ... catch, вероятно, не сработает в условиях программирования обслуживания. Блок CLEAN UP не гарантированно будет выполнен: например, если код «сделать что-то» возвращается рано или каким-то образом выдает что-то, что не является исключением. С другой стороны, деструктор «delete» в моем коде гарантированно будет выполняться в обоих этих случаях (хотя и не в случае завершения программы).

3 голосов
/ 24 декабря 2008

Мой совет: не пытайтесь эмулировать поведение предложения try-finally в C ++. Просто используйте RAII вместо этого. Вы будете жить счастливее.

2 голосов
/ 24 декабря 2008

Чтобы прямо ответить на ваш вопрос, нет .

Это умный способ реализовать эту функциональность, но он ненадежен. Один из способов потерпеть неудачу - это если ваш код «что-то сделать» вызывает исключение, которое не является производным от Exception В этом случае вы никогда не будете delete myObject.

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

2 голосов
/ 24 декабря 2008

Предполагая, что вы хотите удалить указатель myObject и избежать утечек памяти, ваш код все равно может не сделать этого, если в коде есть оператор return, где вы говорите // Do something with myObject. (я предполагаю, что реальный код будет здесь)

Методы RAII имеют соответствующее действие, эквивалентное блоку "finally" в деструкторе конкретного объекта:

class ResourceNeedingCleanup
{
  private:
    void cleanup(); // action to run at end
  public:
    ResourceNeedingCleanup( /*args here*/) {}
    ~ResourceNeedingCleanup() { cleanup(); }  

    void MethodThatMightThrowException();
};

typedef boost::shared_ptr<ResourceNeedingCleanup> ResourceNeedingCleanupPtr;
// ref-counted smart pointer


class SomeObjectThatMightKeepReferencesToResources
{
   ResourceNeedingCleanupPtr pR;

   void maybeSaveACopy(ResourceNeedingCleanupPtr& p)
   {
      if ( /* some condition is met */ )
         pR = p;
   }
};

// somewhere else in the code:
void MyFunction(SomeObjectThatMightKeepReferencesToResources& O)
{
   ResourceNeedingCleanup R1( /*parameters*/) ;
   shared_ptr<ResourceNeedingCleanup> pR2 = 
        new ResourceNeedingCleanup( /*parameters*/ );
   try
   {
      R1.MethodThatMightThrowException();
      pR2->MethodThatMightThrowException();
      O->maybeSaveACopy(pR2);
   }
   catch ( /* something */ )
   {
      /* something */
   }

   // when we exit this block, R1 goes out of scope and executes its destructor
   // which calls cleanup() whether or not an exception is thrown.
   // pR2 goes out of scope. This is a shared reference-counted pointer. 
   // If O does not save a copy of pR2, then pR2 will be deleted automatically
   // at this point. Otherwise, pR2 will be deleted automatically whenever
   // O's destructor is called or O releases its ownership of pR2 and the
   // reference count goes to zero.
}

Я думаю, что у меня правильная семантика; Я сам почти не использовал shared_ptr, но я предпочитаю его auto_ptr <> - указатель на объект может «принадлежать» только одному auto_ptr <>. Я использовал COM CComPtr и его вариант, который я написал для «обычных» (не COM) объектов, который похож на shared_ptr <>, но имеет Attach () и Detach () для передача указателей от одного умного указателя к другому.

...