2015-08-01 1 views
3

У меня есть некоторый фон в Java (и недавно на C#) и хотелось бы лучше узнать C++. Я думаю, что я знаю некоторые основы различий в управлении памятью (и другими ресурсами) между этими языками. Это, возможно, небольшой вопрос, связанный с использованием dispose pattern и различными функциями, доступными на этих языках, чтобы помочь в этом. Мне нравится то, что я собрал из принципов RAII and SBRM, и я пытаюсь понять их дальше.Dispose pattern в C++ vs Java и C#

Пусть у меня есть следующий класс и метод в Java

class Resource implements Closeable { 
    public void close() throws IOException { 
     //deal with any unmanaged resources 
    } 
} 
... 
void useSomeResources() { 
    try(Resource resource = new Resource()) { 
     //use the resource 
    } 
    //do other things. Resource should have been cleaned up. 
} 

или достаточно близко C# аналог

class Resource : IDisposable 
{ 
    public void Dispose() 
    { 
     //deal with any unmanaged resources 
    } 
} 
... 
void UseSomeResources() 
{ 
    using(var resource = new Resource()) 
    { 
     //use the resource 
    } 
    //do other things. Resource should have been cleaned up. 
} 

Правильно ли я думать, что идиома лучше представляя такое же поведение в C++ будет быть следующим?

class Resource { 
    ~Resource() { 
     cleanup(); 
    } 
    public: 
    void cleanup() { 
     //deal with any non-memory resources 
    } 
}; 
... 
void useSomeResources() 
{ 
    { 
     Resource resource; 
     //use the resource 
    } 
    //do other things. Stack allocated resource 
    //should have been cleaned up by stack unwinding 
    //on leaving the inner scope. 
} 

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

Спасибо за любые указатели.

+0

Да, это эквивалент шаблона, потому что «ресурс» будет уничтожен в конце его охватывающей области. – dasblinkenlight

+0

Нет, это не эквивалент шаблона. –

ответ

3

Это почти образец. На самом деле вам не нужно добавлять функцию cleanup(): деструктор должен выполнить очистку.

К сожалению, публичный cleanup() выставлен, допускается случайный звонок cleanup(), в результате чего источник информации находится в нежелательном состоянии.

class Resource { 
    ~Resource() { 
     //deal with any non-memory resources 
    } 
}; // allways ; at the end of a class ;-) 
+0

Спасибо за это - на самом деле я задавался вопросом о уровне доступа к этому методу. Я решил, что для параллельного использования методов Dispose() и close(), которые также могут быть вызваны в любое время, он будет общедоступным. Я согласен с тем, что это опасный способ сделать это, если не принять осторожность, что является частью причины, по которой мне действительно нравится то, что я понимаю в SBRM! Добавили точку с запятой. Ошибка новичков. – Oly

+1

P. Это отчасти потому, что я упомянул идемпотентность для очистки, но, конечно, проблема неожиданно преждевременно очищенных объектов все еще остается. Я думаю, мне понравится C++ :) – Oly

+0

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

0

Вы уже упоминали ответ, это RAII, как и в вашей ссылке.

Типичный класс в C++ будет иметь (виртуальный Вы забыли, что!) Деструктор:

class C { 
    virtual ~C { /*cleanup*/ } 
    }; 

И вы контролируете свою жизнь с обычными правилами блока:

void f() { 
    C c; 

    // stuff 

    // before this exits, c will be destructed 
    } 

Это на самом деле то, что такие языки, как C# и Java, пытаются имитировать свои шаблоны размещения. Поскольку у них нет детерминированных финализаторов, вам необходимо вручную освободить неуправляемые ресурсы (используя using и try соответственно). Однако C++ полностью детерминирован, поэтому сделать это намного проще.

+4

Виртуальный деструктор не нужен и, конечно, не типичен в моем коде. –

+1

Нет смысла создавать виртуальный деструктор, если наследование не задействовано. –

+0

Удостоверьтесь, что вы хорошо используете, потому что не используете их! – Blindy

2

Это (1) предлагаемый класс,

class Resource { 
    ~Resource() { 
     cleanup(); 
    } 
    public: 
    void cleanup() { 
     //deal with any non-memory resources 
    } 
}; 

не является идиоматичен и опасным, потому что (1) он подвергает cleanup операцию, и (2) он предотвращает получения классов от этого и предотвращает автоматические переменные этого класса.

Обнаруживаемый cleanup можно вызвать в любое время любым кодом, а после очистки у вас есть неиспользованный объект зомби. И вы не знаете, когда и если это произойдет, и поэтому код реализации должен проверять это состояние. Очень плохо. Он находится на одном уровне с функциями init, выполняющими роли конструкторов, с помощью только конструктора-заглушки.

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

Правильная модель выглядит следующим образом:

class Resource 
{ 
public: 
    // Whatever, then: 

    ~Resource() 
    { 
     // Clean up. 
    } 
}; 

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

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


(1) Я добавил недостающую точку с запятой. Это хорошая идея, чтобы опубликовать реальный код, даже для небольших общих примеров.

+0

Спасибо, что указали недостающую точку с запятой. Ошибка новичков! Как и в моем ответе на @ Christophe, я ценю советы о том, чтобы сделать его более идиоматичным - похоже, что существование интерфейса IDisposable и Closeable является обходным решением, чтобы справиться с тем, что распределение стека локальных объектов на этих языках невозможно. Таким образом, использование метода (-ов) уничтожения имеет большой смысл в C++. Если деструктор вызывается явно, это фактически не удаляет объект, правда, я прав? Так что разница в том, что код самодокументируется лучше, говоря: «Назовите это на свой страх и риск»? – Oly

+1

@ Oly'Oil'Sourbut: re "имеющий метод уничтожения (private) имеет очень большой смысл в C++", возможно, вы говорите здесь о методе 'cleanup', вызванном из деструктора. Но в случае, если вы этого не сделаете, обратите внимание, что, как упоминалось, деструктор 'private' предотвращает как автоматические переменные, так и (фактически, на практике). Деструктор 'protected' предотвращает только автоматические переменные, поэтому один из способов практически реализовать только динамическое распределение (другим способом, который когда-то был рекомендован, является создание конструкторов non-'public' и предоставление заводских функций, что значительно превосходит Java и C# путь). –

+0

@ Oly'Oil'Sourbut: re "Если деструктор вызывается явно, это фактически не удаляет объект, правда, я прав?", Технически вы правы: он не делает освобождение. Но он закручивает инвариант класса, предположения, на которые полагаются другие методы. Деструкторы обычно вызываются только автоматически, и тогда вам гарантируется один и ровно один вызов деструктора на уничтоженный объект. Примерно в Visual C++ 6.0, в конце 1990-х годов, была ошибка, в которой он дважды вызывал деструктор объекта исключения. Это было немного неприятно, но, к счастью, это произошло только при выполнении «продвинутых» вещей. –

0

Спасибо за любые указатели. Ха!

Одна вещь, которую вы имеете в виду при попытке Java попробовать с помощью метода ресурсов, который является ярлыком для фактического вызова resource.close(). Другой альтернативой будет звонить resource.Dispose()

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

Но есть другие вещи, которые нужно избавиться, когда объекты покинут память. Когда эти объекты покидают сферу действия, это происходит. И вот когда Destructor на C++, вы ссылаетесь выше, вызывается.

Closeable и IDisposable принадлежат к тому, что я называю «Ответственный». Они идут выше и выше того, что любой нормальный «деструктор» для класса будет, когда он удалит объекты из области действия и освободит память верхнего уровня указателей, которые у вас есть. Они также позаботятся о том, что вы можете не думать об этом или, возможно, потенциально поставите систему под угрозу позже. Это как быть отцом и быть хорошим отцом. Отец должен дать своим детям убежище, но хороший отец знает, что лучше для ребенка, даже если дети или другие воспитатели не знают, что лучше для них.

Обратите внимание на то, что важно ссылаться на интерфейсы AutoCloseable не обязательно Closeable интерфейсов, если вы хотите использовать альтернативу «Попробуйте с ресурсами» на Java.

Ответ: IDisposable, интерфейс Closeable и даже интерфейс AutoCloseable, все поддерживают удаление управляемых ресурсов. Так же «Деструктор» на C++, дедушка стенографии для такого процесса удаления. Проблема в том, что вам по-прежнему необходимо обеспечить правильную обработку членов вашего класса, подвергающегося разрушению. Я думаю, что у вас есть правильная функция, чтобы позвонить на C++, чтобы делать то, что вы хотите сделать.

Ссылки: http://www.completecsharptutorial.com/basic/using.php http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

0

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

  • Ответ на главный вопрос: да, automatic local variableresource имеет свой деструктор, вызываемый независимо от того, как элемент управления оставляет блок, в котором он определен. В этом отношении внутренняя область и локальное распределение (обычно подразумевает, что стек, а не куча, но зависит от компилятора) переменной (а не с использованием new) действуют очень похоже на блок try-with-resources в Java или блок использования в C# ,
  • В отличие от Java и C#, возможность выделять объекты в локальном режиме (обычно это означает: в стек) на C++ означает, что для объектов, которые имеют ресурсы, которые нуждаются в безопасном использовании, дополнительные реализации интерфейса и несколько переэкспонированные методы публичного удаления не требуются (и обычно нежелательны).
    • Использование private деструктора ~Resource(), устраняет некоторые опасности случайно, имеющие объекты в неожиданных состояниях (например, файл писатель без дескриптора файла), но «неуправляемые ресурсы» по-прежнему всегда безопасно утилизировать, когда объект удаляется (или выходит за пределы области видимости, если это автоматическая локальная переменная, как в примере вопроса.)
    • Использование элементов функции очистки public по-прежнему абсолютно возможно, если это необходимо, но это часто является ненужной опасностью. Если участник очистки должен быть обнародован, лучше всего быть самим деструктором, так как это очевидное «самодокументирующее» указание любому пользователю, что его следует вызывать только в очень редких случаях: лучше просто использовать delete или позволить локально выделенный объект выходит из области действия и позволяет компилятору выполнять вызов вызова деструктора. Он также устраняет любую путаницу, которую может вызвать общедоступный метод без деструктора («должен ли я звонить cleanup() до того, как я об этом delete?»).
    • Если ваш ресурсный объект должен быть унаследован, важно обеспечить его деструктор как virtual (переопределяемый), так и (как минимум, такой же, как) protected, чтобы обеспечить надлежащее удаление подклассов.
  • Кроме того, при очистке реализованы непосредственно в деструкторах, а мусор коллектора-менее Семантика разрушения сразу после выхода из сферы для автоматических переменных (и на delete для dynamically-allocated variables), он становится имущества и ответственность типа сам по себе, что он обязательно правильно утилизируется, а не просто его можно безопасно утилизировать.

Пример использования более идиоматических C++:

class Resource { 
    //whatever members are necessary 
    public: 
    //resource acquisition for which this object is responsible should be constrained to constructor(s) 
    ~Resource() { //or virtual ~Resource() if inheritance desired 
     //deal with resource cleanup 
    } 
}; 

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