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

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

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

Ответ 1

Для людей, которые комментируют эту тему о 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

Ответ 2

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

Другим является то, что он не работает также на языках без детерминированной очистки.

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

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

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

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

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

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

Ответ 3

Я использую С++ ˚ RAII все время, но я также долгое время разрабатывался в VB6, и 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

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

Ответ 4

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

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

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();
};

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

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

Ответ 5

RAII.

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

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

Прочтите это

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

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

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

Чтобы получить ту же функциональность, наконец,

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 {}, это делает этот метод кодирования более подверженным ошибкам ( i.e. он является пользователем объекта, который должен думать об исключениях). При использовании безопасности RAII безопасность должна быть закодирована один раз, когда объект реализован.

На вопрос, что такое С++. Ответ: Нет.

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

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

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

Ответ 6

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

Ответ 7

Модификация ответа @Pierre:

В 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() вызывается, когда с выходом блока.

Ответ 8

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

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

Ответ 10

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

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

В 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

В Lisp у вас есть 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()

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

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

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

Ответ 11

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

Так что в основном идиома есть, у нее просто нет причудливого имени.

Ответ 15

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

Ответ 16

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

Ответ 17

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