Ответ 1
Я думаю, здесь есть несколько вещей:
- Разница между автоматической переменной и динамически распределенной переменной
- Срок службы объектов
- RAII
- С# parallel
Автоматический vs Динамический
Автоматическая переменная - это переменная, которой система будет управлять временем жизни. Пусть на данный момент крутит глобальные переменные, это осложняется и концентрируется на обычном случае:
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass sc; // 3
sc.foo(); // 4
return 0; // 5
} // 6
Здесь sc
- автоматическая переменная. Он гарантированно будет полностью инициализирован (т.е. Гарантированно будет выполнен конструктор) после успешного выполнения строки (3). Его деструктор будет автоматически вызываться в строке (6).
Мы обычно говорим о сфере действия переменной: от точки объявления до соответствующей закрывающей скобки; и язык гарантирует уничтожение, когда область действия будет удалена, будь то return
или исключение.
Конечно, нет гарантии в случае, если вы вызываете страшное "поведение Undefined", которое обычно приводит к сбою.
С другой стороны, С++ также имеет динамические переменные, то есть переменные, которые вы выделяете с помощью new
.
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass* sc = 0; // 3
sc = new SomeClass(); // 4
sc->foo(); // 5
return 0; // 6
} // 7 (!! leak)
Здесь sc
по-прежнему является автоматической переменной, однако ее тип отличается: теперь он является указателем на переменную типа SomeClass
.
В строке (3) sc
присваивается значение нулевого указателя (nullptr
в С++ 0x), поскольку оно не указывает на какой-либо экземпляр SomeClass
. Обратите внимание, что язык не гарантирует инициализацию самостоятельно, поэтому вам нужно явно назначить что-то иначе, у вас будет значение мусора.
В строке (4) мы создаем динамическую переменную (используя оператор new
) и присваиваем свой адрес sc
. Обратите внимание, что сама динамическая переменная не называется, система дает нам только указатель (адрес).
В строке (7) система автоматически уничтожает sc
, однако она не уничтожает динамическую переменную, на которую она указала, и, следовательно, теперь мы имеем динамическую переменную, адрес которой не хранится нигде. Если мы не используем сборщик мусора (что не соответствует стандарту С++), мы имеем пропущенную память, так как переменная память не будет возвращена до завершения процесса... и даже тогда деструктор не будет запущен ( слишком плохо, если у него были побочные эффекты).
Время жизни объектов
У Херба Саттера есть очень интересные статьи по этому вопросу. Вот первый.
Как резюме:
- Объект живет, как только его конструктор заканчивается. Это означает, что если конструктор бросает, объект никогда не жил (считайте это случайностью беременности).
- Объект мертв, как только его деструктор вызывается, если деструктор выбрасывает (это EVIL), его невозможно повторить, потому что вы не можете вызвать какой-либо метод для мертвого объекта, это поведение undefined.
Если мы вернемся к первому примеру:
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass sc; // 3
sc.foo(); // 4
return 0; // 5
} // 6
sc
жив от строки (4) до строки (5) включительно. В строке (3) она строится (что может закончиться по ряду причин), а в строке (6) она разрушается.
RAII
RAII означает, что "Приобретение ресурсов" является инициализацией. Это идиома для управления ресурсами, и в частности, чтобы убедиться, что ресурсы будут в конечном итоге выпущены после их приобретения.
В С++, поскольку у нас нет коллекции мусора, эта идиома в основном применяется к управлению памятью, но она также полезна для любых других ресурсов: блокировки в многопоточных средах, блокировки файлов, сокеты/соединения в сети и т.д...
При использовании для управления памятью он связывает время жизни динамической переменной с временем жизни заданного набора автоматических переменных, гарантируя, что динамическая переменная не переживет их (и будет потеряна).
В своей простейшей форме он связан с одной автоматической переменной:
int main(int argc, char* argv[])
{
std::unique_ptr<SomeClass> sc = new SomeClass();
sc->foo();
return 0;
}
Это очень похоже на первый пример, за исключением того, что я динамически выделяю экземпляр SomeClass
. Адрес этого экземпляра затем передается объекту sc
типа std::unique_ptr<SomeClass>
(это объект С++ 0x, используйте boost::scoped_ptr
, если он недоступен). unique_ptr
гарантирует, что указанный объект будет уничтожен при уничтожении sc
.
В более сложной форме он может быть связан с несколькими автоматическими переменными, использующими (например) std::shared_ptr
, который, как следует из названия, позволяет обмениваться объектом и гарантирует, что объект будет уничтожен при уничтожении последнего участника, Помните, что это не эквивалентно использованию сборщика мусора, и могут возникнуть проблемы с циклами ссылок, я не буду углубляться здесь, поэтому просто помните, что std::shared_ptr
не является панацеей.
Поскольку очень сложно отлично управлять временем жизни динамической переменной без RAII перед исключениями и многопоточным кодом, рекомендуется следующее:
- максимально использовать автоматические переменные
- для динамических переменных, никогда не вызывайте
delete
самостоятельно и всегда используйте средства RAII
Я лично считаю, что любое появление delete
сильно подозрительно, и я всегда прошу его удалить в обзорах кода: это запах кода.
Параметр С#
В С# в основном используются динамические переменные *
. Вот почему:
- Если вы просто объявляете переменную без присвоения, ее значение равно null: по сути, вы только манипулируете указателями, и вы, таким образом, имеете нулевой указатель (инициализация гарантируется, благодаря доброте).
- Вы используете
new
для создания значений, это вызывает конструктор вашего объекта и дает вам адрес объекта; обратите внимание, как синтаксис похож на С++ для динамических переменных
Однако, в отличие от С++, С# - сбор мусора, поэтому вам не нужно беспокоиться об управлении памятью.
Сбор мусора также означает, что срок жизни объектов более трудно понять: они создаются, когда вы просите их, но уничтожаетесь при удобстве системы. Это может быть проблемой для реализации RAII, например, если вы действительно хотите быстро освободить блокировку, а язык имеет ряд возможностей, чтобы помочь вам выйти из using
ключевого слова + IDisposable
интерфейса из памяти.
*
: легко проверить, если после объявления переменной его значение равно null
, то это будет динамическая переменная. Я считаю, что для int
значение будет 0, что указывает на то, что нет, но прошло уже 3 года, так как я играл с С# для проекта курса, поэтому...