Понимание значения термина и концепции - raii (приобретение ресурсов - это инициализация)

StackOverflow https://stackoverflow.com/questions/712639

Вопрос

Не могли бы вы разработчиков C ++, пожалуйста, дать нам хорошее описание того, что такое Raii, почему это важно, и может ли это иметь какое -либо отношение к другим языкам?

я делать Знай немного. Я считаю, что это означает «приобретение ресурсов - это инициализация». Однако это имя не связано с моим (возможно, неправильным) пониманием того, что такое Raii: у меня сложилось впечатление, что Raii - это способ инициализации объектов в стеке, так что, когда эти переменные выходят из области, деструкторы будут автоматически называться вызывает уборку ресурсов.

Так почему же это не называется «Использование стека для запуска очистки» (UTSTTC :)? Как добраться оттуда в "raii"?

И как вы можете сделать что -то в стеке, что приведет к очистке чего -то, что живет на куче? Кроме того, есть ли случаи, когда вы не можете использовать Raii? Вы когда -нибудь получаете, что желаете сборе мусора? По крайней мере, сборщик мусора, который вы могли бы использовать для некоторых объектов, позволяя другим управлять другим?

Спасибо.

Это было полезно?

Решение

Так почему же это не называется «Использование стека для запуска очистки» (UTSTTC :)?

Райи говорит вам, что делать: приобретите свой ресурс в конструкторе! Я бы добавил: один ресурс, один конструктор. UTSTTC - это лишь одно применение этого, Raii гораздо больше.

Управление ресурсами отстой. Здесь ресурс - это все, что требует очистки после использования. Исследования проектов на многих платформах показывают, что большинство ошибок связаны с управлением ресурсами - и это особенно плохо в Windows (из -за многих типов объектов и распределителей).

В C ++ управление ресурсами особенно сложное из -за комбинации исключений и шаблонов в стиле C ++). За взглядом под капюшоном, посмотрите Gotw8).


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

Давайте начнем с чрезмерно упрощенных FileHandle Класс Используя RAII:

class FileHandle
{
    FILE* file;

public:

    explicit FileHandle(const char* name)
    {
        file = fopen(name);
        if (!file)
        {
            throw "MAYDAY! MAYDAY";
        }
    }

    ~FileHandle()
    {
        // The only reason we are checking the file pointer for validity
        // is because it might have been moved (see below).
        // It is NOT needed to check against a failed constructor,
        // because the destructor is NEVER executed when the constructor fails!
        if (file)
        {
            fclose(file);
        }
    }

    // The following technicalities can be skipped on the first read.
    // They are not crucial to understanding the basic idea of RAII.
    // However, if you plan to implement your own RAII classes,
    // it is absolutely essential that you read on :)



    // It does not make sense to copy a file handle,
    // hence we disallow the otherwise implicitly generated copy operations.

    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;



    // The following operations enable transfer of ownership
    // and require compiler support for rvalue references, a C++0x feature.
    // Essentially, a resource is "moved" from one object to another.

    FileHandle(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
    }

    FileHandle& operator=(FileHandle&& that)
    {
        file = that.file;
        that.file = 0;
        return *this;
    }
}

Если строительство не удается (за исключением), никакая другая функция участника - даже деструктор - не вызвана.

Raii избегает использования объектов в неверном состоянии. Это уже облегчает жизнь, прежде чем мы даже используем объект.

Теперь давайте посмотрим на временные объекты:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
    CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

Есть три случая ошибки для обработки: файл не может быть открыт, можно открыть только один файл, оба файла могут быть открыты, но копирование файлов не удалось. В реализации без RAII, Foo Придется явно справиться со всеми тремя случаями.

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

Теперь давайте собираем некоторые объекты:

class Logger
{
    FileHandle original, duplex;   // this logger can write to two files at once!

public:

    Logger(const char* filename1, const char* filename2)
    : original(filename1), duplex(filename2)
    {
        if (!filewrite_duplex(original, duplex, "New Session"))
            throw "Ugh damn!";
    }
}

Конструктор Logger потерпит неудачу, если originalконструктор не работает (потому что filename1 нельзя было открыть), duplexконструктор не работает (потому что filename2 не удалось открыть) или записать в файлы внутри LoggerТело конструктора терпит неудачу. В любом из этих случаев, LoggerДеспутатель будет нет быть вызванным - поэтому мы не можем полагаться на LoggerDestructor выпустить файлы. Но если original был построен, его деструктор будет вызван во время очистки Logger конструктор.

Raii упрощает очистку после частичной конструкции.


Отрицательные моменты:

Негативные моменты? Все проблемы могут быть решены с помощью RAII и умных указателей ;-)

Raii иногда является громоздким, когда вам нужно задержать задержку, выдвигая агрегированные объекты на кучу.
Представьте, что регистрации нужен SetTargetFile(const char* target). Анкет В этом случае ручка, которая все еще должна быть членом Logger, необходимо проживать в куче (например, в умном указателе, чтобы правильно запустить разрушение ручки.)

Я никогда не желал коллекции мусора. Когда я делаю C#, я иногда чувствую минутку блаженства, что мне просто не нужно заботиться, но гораздо больше я скучаю по всем прохладным игрушкам, которые можно создать посредством детерминированного разрушения. (с использованием IDisposable просто не разрезает.)

У меня была одна особенно сложная структура, которая могла бы извлечь выгоду из GC, где «простые» умные указатели вызывали бы круговые ссылки на несколько классов. Мы запутались, тщательно уравновешивая сильные и слабые указатели, но в любое время, когда мы хотим что -то изменить, мы должны изучать большую диаграмму отношений. GC мог бы быть лучше, но некоторые из компонентов содержали ресурсы, которые должны быть выпущены как можно скорее.


Примечание на примере FileHandle: оно не должно было быть завершено, просто образец - но оказался неверным. Спасибо Йоханнесу Шаубу за указание и Fredoverflow за то, что он превратил его в правильное решение C ++ 0x. Со временем я согласился с подходом задокументировано здесь.

Другие советы

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

0. Raii - это прицелы

Raii - это оба:

  1. Приобретение ресурса (независимо от того, какой ресурс) в конструкторе и не приобретать его в деструкторе.
  2. выполнение конструктора, выполненного при объявленной переменной, и деструктор автоматически выполняется, когда переменная выходит из области.

Другие уже ответили на это, поэтому я не буду уточнить.

1. При кодировании в Java или C#вы уже используете raii ...

Месье Журдейн: Что! Когда я говорю: «Николь, принеси мне мои тапочки и дай мне мою ночную колпаку», это проза?

Мастер философии: Да, сэр.

Месье Журдейн: В течение более сорока лет я говорил о прозе, ничего об этом ничего не зная, и я очень обязан вам за то, что научил меня этому.

- Molière: джентльмен среднего класса, акт 2, сцена 4

Как сделал месье Журден с прозой, C# и даже Java люди уже используют Raii, но скрытыми способами. Например, следующий код Java (который написан одинаково в C# путем замены synchronized с lock):

void foo()
{
   // etc.

   synchronized(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

... уже использует RAII: приобретение Mutex выполнено в ключевом слова (synchronized или же lock), и Uncquisition будет выполнено при выходе из области.

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

Преимущество C ++ имеет java и c# Вот что все может быть сделано с помощью RAII. Например, не существует прямой встроенной эквивалента из synchronized ни lock В C ++, но мы все еще можем иметь их.

В C ++ это будет написано:

void foo()
{
   // etc.

   {
      Lock lock(someObject) ; // lock is an object of type Lock whose
                              // constructor acquires a mutex on
                              // someObject and whose destructor will
                              // un-acquire it 

      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

который может быть легко записан, java/c# way (с помощью макросов C ++):

void foo()
{
   // etc.

   LOCK(someObject)
   {
      // if something throws here, the lock on someObject will
      // be unlocked
   }

   // etc.
}

2. Райи имеют альтернативное использование

Белый кролик: [поет] Я опоздал / я опоздал / на очень важную дату. / Нет времени сказать «Привет». / До свидания. / Я опоздал, я опоздал, я опоздал.

- Алиса в стране чудес (Disney Version, 1951)

Вы знаете, когда будет вызван конструктор (при объявлении объекта), и вы знаете, когда его соответствующий деструктор будет вызван (на выходе из области), так что вы можете написать почти волшебный код с линией. Добро пожаловать в Страну чудес C ++ (по крайней мере, с точки зрения разработчика C ++).

Например, вы можете написать Counter Object (я позволяю этому в качестве упражнения) и использовать его, просто объявив его переменную, как использовался объект блокировки выше:

void foo()
{
   double timeElapsed = 0 ;

   {
      Counter counter(timeElapsed) ;
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

Что, конечно, можно написать, опять же, java/c# way с использованием макро:

void foo()
{
   double timeElapsed = 0 ;

   COUNTER(timeElapsed)
   {
      // do something lengthy
   }
   // now, the timeElapsed variable contain the time elapsed
   // from the Counter's declaration till the scope exit
}

3. Почему C ++ не хватает finally?

Кричать] Это финал Обратный отсчет!

-Европа: последний обратный отсчет (извините, я был вне цитат, здесь ... :-)

А finally Пункт используется в C#/Java для обработки утилизации ресурсов в случае выхода по объему (либо через return или брошенное исключение).

Проницательные спецификации читатели заметят, что C ++ не имеет, наконец, пункт. И это не ошибка, потому что C ++ не нуждается в ней, поскольку RAII уже обрабатывает утилизацию ресурсов. (И поверьте мне, написание деструктора C ++ - это величины проще, чем написание правильного предложения Java, наконец, или даже правильного метода утилизации C#).

Тем не менее, иногда finally Пункт был бы крутым. Можем ли мы сделать это в C ++? Да мы можем! И снова с альтернативным использованием raii.

Заключение: RAII - это более чем философия в C ++: это C ++

Райи? Это C ++ !!!

- Возмущенный комментарий разработчика C ++, беззастенчиво скопированный неясным королем Спарты и его 300 друзьями

Когда вы достигаете некоторого уровня опыта в C ++, вы начинаете думать с точки зрения Райи, с точки зрения Конструкторы и деструкторы Автоматизированное выполнение.

Вы начинаете думать с точки зрения прицелы, и { а также } Персонажи становятся самыми важными в вашем коде.

И почти все подходит правильно с точки зрения RAII: безопасность исключений, мутекс, подключения к базе данных, запросы базы данных, подключение к серверу, часы, ручки ОС и т. Д., И последнее, но не менее важное, память.

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

И как головоломка, все подходит.

RAII - это такая большая часть C ++, C ++ не может быть C ++ без него.

Это объясняет, почему опытные разработчики C ++ так очарованы Raii, и почему Raii - это первое, что они ищут, когда пробуют другой язык.

И это объясняет, почему сборщик мусора, хотя сам по себе великолепная технология не так впечатляет с точки зрения разработчика C ++:

  • Raii уже занимается большей частью дел, рассматриваемых GC
  • GC занимается лучше, чем Raii с круговыми ссылками на чистые управляемые объекты (смягченные умными использованием слабых указателей)
  • Тем не менее, GC ограничен памятью, в то время как Raii может обрабатывать любой вид ресурса.
  • Как описано выше, Raii может сделать намного больше ...

RAII использует семантику деструкторов C ++ для управления ресурсами. Например, рассмотрим умный указатель. У вас есть параметризованный конструктор указателя, который инициализирует этот указатель с адресом объекта. Вы распределяете указатель на стеке:

SmartPointer pointer( new ObjectClass() );

Когда умный указатель выйдет из области, деструктор класса Pointer удаляет связанный объект. Указатель распределяется в стеке, а объект-выделяется кучей.

Есть определенные случаи, когда Raii не помогает. Например, если вы используете интеллектуальные интеллектуальные указатели (например, Boost :: shared_ptr) и создаете графическую структуру с циклом, с которым вы рискуете с утечкой памяти, потому что объекты в цикле предотвратят высвобождение друг друга. Сбор мусора поможет против этого.

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

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

Рациональным, стоящим за RAII, состоит в том, чтобы сделать исключение приобретения ресурсов безопасным. Что все приобретенные ресурсы должным образом выпускаются независимо от того, где происходит исключение. Однако это зависит от качества класса, который приобретает ресурс (это должно быть безопасным исключением, и это сложно).

Я хотел бы выразить это немного сильнее, чем предыдущие ответы.

Райи, Приобретение ресурсов - это инициализация означает, что все приобретенные ресурсы должны быть приобретены в контексте инициализации объекта. Это запрещает «голый» приобретение ресурсов. Обоснование состоит в том, что очистка в C ++ работает на основе объекта, а не на основе функций. Следовательно, вся очистка должна выполняться объектами, а не функциональными вызовами. В этом смысле C ++ более ориентирован на объект, например, Java. Очистка Java основана на вызовах функций в finally положения.

Проблема сбора мусора заключается в том, что вы теряете детерминированное разрушение, которое имеет решающее значение для RAII. Как только переменная выйдет из области, она зависит от коллекционера мусора, когда объект будет восстановлен. Ресурс, который удерживается объектом, будет продолжаться до тех пор, пока деструктор не будет вызван.

RAII происходит от распределения ресурсов является инициализацией. По сути, это означает, что когда конструктор завершает выполнение, построенный объект полностью инициализируется и готов к использованию. Это также подразумевает, что деструктор выпустит любые ресурсы (например, память, ресурсы ОС), принадлежащие объекту.

По сравнению с мусором, собранными языками/технологиями (например, Java, .net), C ++ позволяет полностью контролировать срок службы объекта. Для стека, выделенного объектом, вы узнаете, когда будет вызван деструктор объекта (когда выполнение выходит из области), то, что на самом деле не контролируется в случае сбора мусора. Даже используя интеллектуальные указатели в C ++ (например, Boost :: Shared_ptr), вы будете знать, что когда нет ссылки на заостренный объект, будет вызван деструктор этого объекта.

И как вы можете сделать что -то в стеке, что приведет к очистке чего -то, что живет на куче?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete[] m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

Когда возникает экземпляр Int_buffer, он должен иметь размер, и он выделяет необходимую память. Когда это выходит из сфера, это называется деструктор. Это очень полезно для таких вещей, как объекты синхронизации. Рассмотреть возможность

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

Кроме того, есть ли случаи, когда вы не можете использовать Raii?

Нет, не совсем.

Вы когда -нибудь получаете, что желаете сборе мусора? По крайней мере, сборщик мусора, который вы могли бы использовать для некоторых объектов, позволяя другим управлять другим?

Никогда. Сбор мусора решает только очень небольшую подмножество динамического управления ресурсами.

Здесь уже много хороших ответов, но я бы просто хотел добавить:
Простое объяснение RAII заключается в том, что в C ++ объект, выделенный на стеке, разрушается всякий раз, когда он выходит из сферы действия. Это означает, что деструктор объектов будет вызван и может сделать всю необходимую очистку.
Это означает, что если объект создается без «нового», «удалить» не требуется. И это также идея «умных указателей» - они находятся в стеке и, по сути, завершают объект на основе кучи.

RAII является аббревиатурой для приобретения ресурсов - это инициализация.

Этот метод очень уникален для C ++ из -за их поддержки как конструкторов, так и деструкторов, и почти автоматически конструкторы, которые соответствуют тем, что аргументы передаются, или в худшем случае, который конструктор по умолчанию называется и деструктора, если явность предоставлена, иначе называется по умолчанию. Это добавляется компилятором C ++, вызывается, если вы не писали деструктор явно для класса C ++. Это происходит только для объектов C ++, которые являются автоматическими управляющими - что не использует бесплатный хранилище (память выделена/сделку с использованием новых, новых []/удаления, удаления [] операторов C ++).

Техника RAII использует эту функцию автоматического управления объектом для обработки объектов, которые создаются в куче/свободном магазине, объясняющим, прося больше памяти, используя новую/новую [], которая должна быть явно разрушена, позвонив по удалению/удалению [] Анкет Класс объекта с автоматическим управлением завершит этот другой объект, который создается в памяти кучи/свободного магазина. Следовательно, когда запускается конструктор объекта, управляемого автоматическим управлением, обернутый объект создается в памяти кучи/свободного магазина, и когда рукоятка с автопроизводным объектом выходит из области, деструктор этого автоматического объекта называется автоматически, в котором обернутый Объект уничтожается с помощью Delete. С помощью концепций OOP, если вы оберните такие объекты в другой класс в частном масштабе, у вас не будет доступа к участникам и методам оберщенных классов, и это причина, по которой разрабатываются умные указатели (он же ручки). Эти умные указатели выявляют обернутый объект как напечатанный объект для внешнего мира и там, позволяя вызывать любые участники/методы, из которых представлен обнаженный объект памяти. Обратите внимание, что интеллектуальные указатели имеют различные ароматы на основе различных потребностей. Вам следует обратиться к современному программированию C ++ от Andrei Alexandrescu или Boost Library's (www.boostorg) реализация/документация/документация по этому вопросу, чтобы узнать об этом. Надеюсь, это поможет вам понять Райи.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top