Используют ли, знают или понимают RAII программисты других языков, помимо C ++?

Я заметил, что RAII привлекает много внимания к Stackoverflow, но в моих кругах (в основном C ++) RAII настолько очевиден, что это все равно что спрашивать, что это за класс или деструктор.

Так что мне действительно любопытно, потому ли это потому, что я ежедневно окружен заядлыми программистами на C ++, а RAII просто не так хорошо известен в целом (включая C ++), или все эти вопросы по Stackoverflow связаны с тем фактом, что что сейчас я общаюсь с программистами, которые не выросли на C ++, а люди на других языках просто не используют / не знают о RAII?


person Robert Gould    schedule 03.10.2008    source источник
comment
Еще раз ТАК доказывает, что это того стоит. Обычно я склоняюсь к такому программированию, но не знал, что это формализовано и называется RAII. Спасибо.   -  person slashmais    schedule 03.10.2008
comment
Думают ли программисты BASIC об OEG1K (On Error Goto 1000)?   -  person rwong    schedule 04.04.2011
comment
В других языках иногда используется идиома выполнения для достижения аналогичного поведения.   -  person StackedCrooked    schedule 02.03.2012


Ответы (17)


Для людей, которые комментируют в этой ветке информацию о RAII (получение ресурсов - это инициализация), вот мотивационный пример.

class StdioFile {
    FILE* file_;
    std::string mode_;

    static FILE* fcheck(FILE* stream) {
        if (!stream)
            throw std::runtime_error("Cannot open file");
        return stream;
    }

    FILE* fdup() const {
        int dupfd(dup(fileno(file_)));
        if (dupfd == -1)
            throw std::runtime_error("Cannot dup file descriptor");
        return fdopen(dupfd, mode_.c_str());
    }

public:
    StdioFile(char const* name, char const* mode)
        : file_(fcheck(fopen(name, mode))), mode_(mode)
    {
    }

    StdioFile(StdioFile const& rhs)
        : file_(fcheck(rhs.fdup())), mode_(rhs.mode_)
    {
    }

    ~StdioFile()
    {
        fclose(file_);
    }

    StdioFile& operator=(StdioFile const& rhs) {
        FILE* dupstr = fcheck(rhs.fdup());
        if (fclose(file_) == EOF) {
            fclose(dupstr); // XXX ignore failed close
            throw std::runtime_error("Cannot close stream");
        }
        file_ = dupstr;
        return *this;
    }

    int
    read(std::vector<char>& buffer)
    {
        int result(fread(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }

    int
    write(std::vector<char> const& buffer)
    {
        int result(fwrite(&buffer[0], 1, buffer.size(), file_));
        if (ferror(file_))
            throw std::runtime_error(strerror(errno));
        return result;
    }
};

int
main(int argc, char** argv)
{
    StdioFile file(argv[1], "r");
    std::vector<char> buffer(1024);
    while (int hasRead = file.read(buffer)) {
        // process hasRead bytes, then shift them off the buffer
    }
}

Здесь, когда создается экземпляр StdioFile, приобретается ресурс (в данном случае файловый поток); когда он уничтожен, ресурс высвобождается. Блокировка try или finally не требуется; если чтение вызывает исключение, автоматически вызывается fclose, потому что он находится в деструкторе.

Деструктор гарантированно вызывается, когда функция покидает main, как обычно, так и в виде исключения. В этом случае файловый поток очищается. Мир снова в безопасности. :-D

person Chris Jester-Young    schedule 03.10.2008
comment
Добавьте код, чтобы показать, что он используется. Чтобы объяснить, почему это делает исключение кода безопасным! - person Martin York; 03.10.2008
comment
Хорошо, вот мой первый удар; дайте мне знать, что вы думаете. :-) - person Chris Jester-Young; 03.10.2008
comment
Не уверен, почему за этот ответ проголосовали. Вопрос не в том, что такое RAII, а в том, какое место занимает эта концепция среди программистов, не работающих на C ++. - person ApplePieIsGood; 28.12.2008
comment
Это все еще хорошее объяснение, так что я могу понять голосование. Однако выбор его в качестве ответа немного странный. :) - person jalf; 28.12.2008
comment
Это не особо помогает мне идентифицировать его по тому, что он элегантно обрабатывает, поэтому вам не нужно об этом беспокоиться. Конечно, поймите это, но сделайте это привычным. И это действительно в первую очередь о C ++. - person dkretz; 17.01.2009
comment
Не гарантируется, что деструктор будет вызываться после main, если нет блоков try / catch и выдается исключение, то есть определяется реализацией, и MSVC не будет его вызывать. - person paulm; 13.01.2014
comment
Разве это не в основном Python с оператором with? Использование объекта c ++ вместо контекста. - person les; 27.01.2019
comment
@les Python with похож на using в C #: вы должны не забывать его использовать, иначе у вас будет утечка. RAII C ++ используется по умолчанию, и вам нужно сделать специальные вещи, чтобы отключить его (например, new объект в необработанный указатель (что является большим запретом в современном C ++) и утечка этого). - person Chris Jester-Young; 29.01.2019

Существует множество причин, по которым RAII не более известен. Во-первых, название не особо очевидное. Если бы я еще не знал, что такое RAII, я бы никогда не догадался об этом по названию. (Получение ресурсов - это инициализация? Какое отношение это имеет к деструктору или очистке, что на самом деле характеризует RAII?)

Во-вторых, он не работает так же хорошо на языках без детерминированной очистки.

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

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

Тем не менее, все еще можно создать свою собственную оболочку в стиле RAII на C # или других языках. Некоторое время назад я делал это на C #. Мне нужно было что-то написать, чтобы соединение с базой данных было закрыто сразу после использования, задача, которую любой программист на C ++ сочтет очевидным кандидатом на использование RAII. Конечно, мы могли бы обернуть все в using-операторы всякий раз, когда использовали соединение с базой данных, но это просто беспорядочно и подвержено ошибкам.

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

T RAIIWrapper<T>(Func<DbConnection, T> f){
  using (var db = new DbConnection()){
    return f(db);
  }
}

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

person jalf    schedule 28.12.2008
comment
Я разработчик .Net по своему опыту, так что не могли бы вы пояснить, почему «использование» по-прежнему вызывает проблемы (беспорядочные и подверженные ошибкам)? Является ли это тем, что ресурсы в области 'using' не удаляются мгновенно после выхода из области, а вместо этого ожидают сборки мусора? Если это так, то в каких случаях это может привести к ошибкам? - person wiz_lee; 01.02.2017

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

Фактически, многие программы VB6 в значительной степени полагаются на RAII. Одно из наиболее любопытных применений, которое я неоднократно видел, - это следующий небольшой класс:

' WaitCursor.cls '
Private m_OldCursor As MousePointerConstants

Public Sub Class_Inititialize()
    m_OldCursor = Screen.MousePointer
    Screen.MousePointer = vbHourGlass
End Sub

Public Sub Class_Terminate()
    Screen.MousePointer = m_OldCursor
End Sub

Использование:

Public Sub MyButton_Click()
    Dim WC As New WaitCursor

    ' … Time-consuming operation. '
End Sub

После завершения трудоемкой операции исходный курсор восстанавливается автоматически.

person Konrad Rudolph    schedule 03.10.2008

RAII означает Инициализация получения ресурсов. Это совершенно не зависит от языка. Эта мантра здесь, потому что C ++ работает так, как работает. В C ++ объект не создается до завершения его конструктора. Деструктор не будет вызван, если объект не был успешно построен.

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

class OhMy {
public:
    OhMy() { p_ = new int[42];  jump(); } 
    ~OhMy() { delete[] p_; }

private:
    int* p_;

    void jump();
};

Если вызов jump() в конструкторе вызывает проблемы, у нас возникают проблемы, потому что p_ будет протекать. Мы можем исправить это так:

class Few {
public:
    Few() : v_(42) { jump(); } 
    ~Few();

private:
    std::vector<int> v_;

    void jump();
};

Если люди не знают об этом, то это по одной из двух причин:

  • Они плохо знают C ++. В этом случае им следует снова открыть TCPPPL, прежде чем писать следующий класс. В частности, об этой технике рассказывается в разделе 14.4.1 третьего издания книги.
  • Они вообще не знают C ++. Это нормально. Эта идиома очень похожа на C ++ y. Либо выучите C ++, либо забудьте обо всем этом и продолжайте жить своей жизнью. Желательно изучить C ++. ;)
person wilhelmtell    schedule 03.10.2008

RAII.

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

Что делает RAII лучше, чем finally и такие механизмы, так это то, что он делает код более безопасным в использовании, потому что он перекладывает ответственность за правильное использование объекта с пользователя объекта на дизайнера объекта.

Прочтите это

Пример использования StdioFile правильно используя RAII.

void someFunc()
{
    StdioFile    file("Plop","r");

    // use file
}
// File closed automatically even if this function exits via an exception.

Чтобы получить такую ​​же функциональность с файлом finally.

void someFunc()
{
      // Assuming Java Like syntax;
    StdioFile     file = new StdioFile("Plop","r");
    try
    {
       // use file
    }
    finally
    {
       // close file.
       file.close(); // 
       // Using the finaliser is not enough as we can not garantee when
       // it will be called.
    }
}

Поскольку вам нужно явно добавить блок try {} finally {}, это делает этот метод кодирования более подверженным ошибкам (т.е. именно пользователь объекта должен думать об исключениях). При использовании RAII безопасность исключений должна кодироваться один раз при реализации объекта.

На вопрос, специфичен ли этот C ++.
Краткий ответ: Нет.

Более длинный ответ:
Для этого требуются конструкторы / деструкторы / исключения и объекты с определенным временем жизни.

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

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

person Martin York    schedule 03.10.2008

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

person ApplePieIsGood    schedule 28.12.2008

Модификация @ Ответ Пьера:

В Python:

with open("foo.txt", "w") as f:
    f.write("abc")

f.close() вызывается автоматически вне зависимости от того, возникло ли исключение.

Обычно это можно сделать с помощью contextlib.closing , из документации:

closing(thing): вернуть диспетчер контекста, который закрывает объект по завершении блока. Это в основном эквивалентно:

from contextlib import contextmanager

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

И позволяет писать такой код:

from __future__ import with_statement # required for python version < 2.6
from contextlib import closing
import urllib

with closing(urllib.urlopen('http://www.python.org')) as page:
    for line in page:
        print line

без необходимости явно закрывать страницу. Даже в случае возникновения ошибки page.close () будет вызываться при выходе из блока with.

person jfs    schedule 03.10.2008

Common Lisp имеет RAII:

(with-open-file (stream "file.ext" :direction :input)
    (do-something-with-stream stream))

См. http://www.psg.com/~dlamkins/sl/chapter09.html

person Daniel Earwicker    schedule 27.02.2009

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

Итак, мой секрет ... Я полагаю, что это было бы то, что я читал Мейерса, Саттера [EDIT:] и Андрея все время много лет назад, пока я просто не проглотил его.

person Robert Gould    schedule 03.10.2008
comment
Я думаю, что многие люди знают концепцию, но не знают терминологии. - person mattlant; 03.10.2008
comment
Возможно, они, вероятно, не связывают эти два понятия. - person Robert Gould; 03.10.2008
comment
Именно так я узнал о RAII. Спасибо Мейерсу, Саттерсу и Андрею! - person nullDev; 03.10.2008
comment
Я изучил C ++ много лет назад, поэтому этот акроним для меня ничего не значил. Это как при ходьбе спрашивать о технике LBW. LBW? Что это? Смотрите в обе стороны. Ну, конечно, я делаю это, когда перехожу улицу. Почему ты вообще этого не сказал? И какой смысл задавать вопрос? - person Jon Ericson; 15.10.2008

Дело в том, что RAII требует детерминированной финализации, что гарантировано для объектов на основе стека в C ++. Такие языки, как C # и Java, которые полагаются на сборку мусора, не имеют этой гарантии, поэтому ее нужно как-то «прикрутить». В C # это делается путем реализации IDisposable, и многие из тех же шаблонов использования затем возникают в основном, что является одним из мотиваторов для оператора using, он обеспечивает Disposal и очень хорошо известен и используется.

В общем, идиома есть, просто у нее нет причудливого названия.

person Torbjörn Gyllebring    schedule 03.10.2008
comment
ПРИМЕЧАНИЕ: «using» адресует только локальные переменные функции - или, скорее, объекты, время жизни которых полностью ограничено одним кадром стека. он не обращается к статике класса, статике функций или членам класса, или объектам, выделенным в куче, срок жизни которых ограничен другими способами. - person Aaron; 03.10.2008
comment
IDispose! = RAII. Вы, пользователь объекта, должны размещать операторы using везде, чтобы получить тот же эффект, и даже в этом случае это не сработает, если в объект встроены другие объекты. IDispose - это синтаксический сахар для вызова close для каждого объекта, а не RAII. - person gbjbaanb; 11.10.2008
comment
С # позволяет вам выделять память в стеке, однако я думаю, что вы потеряли гарантию безопасности типа, поскольку вы по существу работаете с необработанной памятью, и все ставки в управляемом мире отключены, когда вы это делаете. - person ApplePieIsGood; 28.12.2008
comment
Идиомы нет в таких языках, как C # - пожалуйста, используйте C ++ какое-то время, и вы увидите, что вы только что сказали, было чушью. - person 1800 INFORMATION; 17.01.2009

RAII - это способ в C ++ убедиться, что процедура очистки выполняется после блока кода независимо от того, что происходит в коде: код выполняется до конца должным образом или вызывает исключение. Уже процитированный пример автоматически закрывает файл после его обработки, см. ответьте здесь.

На других языках вы используете другой механизм для этого.

В Java вы можете попробовать конструкции {} finally {}:

try {
  BufferedReader file = new BufferedReader(new FileReader("infilename"));
  // do something with file
}
finally {
    file.close();
}

В Ruby у вас есть аргумент автоматического блока:

File.open("foo.txt") do | file |
  # do something with file
end

В Лиспе у вас есть unwind-protect и предопределенный with-XXX

(with-open-file (file "foo.txt")
  ;; do something with file
)

В схеме у вас есть dynamic-wind и предопределенный with-XXXXX:

(with-input-from-file "foo.txt"
  (lambda ()
    ;; do something 
)

в Python вы наконец попробовали

try
  file = open("foo.txt")
  # do something with file
finally:
  file.close()

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

Другие примеры RAII:

  • разблокировка мьютекса после приобретения
  • закрытие соединения с базой данных после открытия
  • освобождение памяти после выделения
  • регистрация при входе и выходе блока кода
  • ...
person Pierre    schedule 03.10.2008
comment
Решение C ++ лучше, потому что код очистки записывается один раз (либо в деструкторе, либо с использованием интеллектуального указателя), и порядок уничтожения будет соблюдаться автоматически, в отличие от неуклюжих шаблонов, показанных в ваших примерах. - person paercebal; 03.10.2008
comment
И я даже не буду говорить о сборщике мусора на некоторых языках (например, C #), выполняющейся в то же время, в другом потоке, чем ваш финализатор, что приводит к забавному выводу другого вопроса в SO: не используйте RAII для удаления управляемые ресурсы на C #. - person paercebal; 03.10.2008
comment
В Python вы можете использовать оператор with. Смотрите мой ответ. - person jfs; 03.10.2008
comment
Использование with в Python или использование в C # является худшим - это возлагает на пользователя класса бремя использования этих ключевых слов. С деструкторами нагрузка ложится на писателя классов, и это нужно делать только один раз. - person Nemanja Trifunovic; 03.10.2008
comment
@ Неманья Трифунович: Похоже, вы не понимаете, как оператор with работает в Python. Вы пишете диспетчер контекста только один раз. Сравните with Lock(obj) as l:... в Python и { Lock l(obj); ...} в C ++, например, оба могут вызывать obj. acquire() при входе в блок и obj.release() при выходе. - person jfs; 17.01.2009
comment
Нет необходимости создавать новый класс для каждого отдельного действия по очистке - вы можете использовать ScopeGuard Алексея Александреску (google it), чтобы гарантировать, что любая функция, которую вы хотите предоставить, вызывается при выходе из области видимости. - person j_random_hacker; 25.01.2009

Это как бы связано с тем, чтобы знать, когда будет вызван ваш деструктор, верно? Так что он не полностью независим от языка, учитывая, что это не дано во многих языках с GC.

person Community    schedule 03.10.2008
comment
Конечно, это не зависит от языка. Многие идиомы и шаблоны недоступны или их трудно реализовать на разных языках из-за отсутствия у них функций. - person 1800 INFORMATION; 17.01.2009

Я думаю, что многие другие языки (например, те, в которых нет delete) не предоставляют программисту такой же контроль над временем жизни объектов, и поэтому должны быть другие средства для обеспечения детерминированного распределения ресурсов. В C #, например, часто используется using с IDisposable.

person Chris Jester-Young    schedule 03.10.2008

RAII популярен в C ++, потому что это один из немногих (только?) Языков, которые могут выделять сложные локальные переменные области видимости, но не имеют предложения finally. У C #, Java, Python, Ruby есть finally или эквивалент. C не имеет finally, но также не может выполнять код, когда переменная выходит за пределы области видимости.

person John Millikin    schedule 03.10.2008
comment
C ++ действительно нужен, наконец, из-за RAII (преднамеренное решение). Эти другие языки нужны, наконец, потому что у них нет RAII. наконец, это не хороший механизм контроля, это пластырь, добавленный для решения проблем. - person Martin York; 03.10.2008
comment
Мартин: Слушайте, слушайте! (Я полагаю, он / делает / не делает /?) - person Chris Jester-Young; 03.10.2008
comment
Прости. Дислексия и быстрый набор текста - не лучшее сочетание. В конце концов, C ++ НЕ нужен. - person Martin York; 03.10.2008
comment
RAII имеет свой собственный набор проблем, таких как чрезвычайно сложные деструкторы и огромное количество классов для обработки различных видов получения ресурсов. - person John Millikin; 03.10.2008
comment
@ Джон: Да, есть цена. Но это перекладывает ответственность с пользователя объекта на его дизайнера. - person Martin York; 03.10.2008
comment
@Martin: Я согласен, чрезвычайно сложные деструкторы только дублируют код, который нужно было бы вырезать и вставить, неизвестно сколько блоков finally. RAII снижает вероятность ошибки - person 1800 INFORMATION; 17.01.2009
comment
@John: Вам не нужны новые классы, если вы используете ScopeGuard, хотя в настоящее время вам нужно написать функцию для каждого типа освобождения. Если эта функция уже существует (например, fclose ()), она вам даже не нужна. Все будет еще лучше, когда мы получим лямбда-выражения в C ++ 1x ... - person j_random_hacker; 25.01.2009

У меня есть коллеги, которые жестко относятся к типам C ++, "читают спецификацию". Многие из них знают RAII, но я никогда не слышал, чтобы он использовался за пределами этой сцены.

person Michael Easter    schedule 11.10.2008

CPython (официальный Python, написанный на C) поддерживает RAII из-за использования объектов с подсчетом ссылок с немедленным уничтожением на основе области (а не при сборке мусора). К сожалению, Jython (Python на Java) и PyPy не поддерживают эту очень полезную идиому RAII, и она ломает много унаследованного кода Python. Таким образом, для портативного Python вам нужно обрабатывать все исключения вручную, как и в Java.

person Community    schedule 11.10.2008
comment
способ работы с памятью в C-расширении python не имеет ничего общего с RAII и, конечно, не зависит от области видимости. Я бы хотел, чтобы это было так, потому что как бы я ни ненавидел C ++, RAII - единственное, что делает C ++ лучше, чем C в этой области. - person David Cournapeau; 09.07.2009
comment
Раньше я думал, что Python с его схемой подсчета ссылок поддерживает RAII ... Но это не так. Хорошо, есть некоторые ситуации, когда это действительно работает, но это очень быстро усложняется, особенно когда интерпретатор завершает работу и модули выгружены. - person Arafangion; 18.07.2010

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

person yfeldblum    schedule 03.10.2008
comment
Неправильно. Ему нужна контролируемая продолжительность жизни. Это не ограничивается объектами стека. НАПРИМЕР. Perl может работать с RAII и использует объекты с подсчетом ссылок. - person Martin York; 03.10.2008