Приобретение ресурса инициализации "RAII" - PullRequest
1 голос
/ 20 марта 2010

в приведенном ниже примере

class X
{
    int *r;
public: 
    X() {
        cout << "X is created";
        r = new int[10];
    };
    ~X() {
        cout<< "X is destroyed";
        delete [] r;
    };
};
class Y
{
public: 
    Y() {
        X x;
        throw 44;
    }; 
    ~Y() {
        cout << "Y is destroyed";
    };
};

Я получил этот пример RAII с одного сайта и у меня есть некоторые сомнения. пожалуйста, помогите.

  1. в конструкторе x мы не рассматриваем сцену "если выделение памяти не удастся".
  2. Здесь для деструктора Y безопасно, так как в y конструктор не выделяет никакой памяти. Что делать, если нам нужно сделать выделение памяти также в конструкторе y?

Ответы [ 2 ]

9 голосов
/ 20 марта 2010

В конструкторе X, если new терпит неудачу, он генерирует исключение (std::bad_alloc). Это означает, что конструктор никогда не завершается, поэтому время жизни объекта никогда не начинается, поэтому его деструктор никогда не вызывается (объекта нет), и между new[] и delete[] нет несоответствия. (X должен иметь объявленный пользователем конструктор копирования и объявленный пользователем оператор копирования, поскольку предоставленная реализация нарушит эту гарантию, если построение завершится успешно, и объект будет скопирован или назначен.)

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

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

8 голосов
/ 20 марта 2010

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

Тебе не обязательно. В случае неудачи конструктор сгенерирует std::bad_alloc. I.e.:

  1. Y конструктор называется.
  2. X конструктор называется.
  3. Не удается выделить new int[], выбрасывая std::bad_alloc. Память никогда не выделяется.
  4. Поскольку X никогда не завершал построение, конструктор Y завершается неудачно, и Y никогда не завершает построение.

Поэтому утечек нет.

Здесь для деструктора Y безопасно, так как в y construcotr не выделяет никакой памяти. Что делать, если нам нужно сделать некоторое выделение памяти также в конструкторе y?

У вас все еще нет проблем. Сбои распределения выбросят std::bad_alloc. Ответственность за этот провал несут те, кто использует ваш класс.

  1. Y конструктор называется.
  2. X конструктор называется.
  3. Распределение new int[] выполнено успешно.
  4. Конструктор Y теперь как-то дает сбой и должен вызвать исключение (например, ошибка выделения).
  5. Механизм создания исключений раскручивает стек вызовов и вызывает деструкторы для любых локальных переменных, в данном случае включая X.
  6. X delete[] s new int[].

Опять же, здесь нет утечек.

Обратите внимание, что вам нужно с осторожностью относиться к множественным распределениям. I.e.:

class Foo
{
    int * r;
public:
    Foo() {
        r = new int;
        throw myException;
    };
    ~Foo() {
        delete r;
    };
};

СЕЙЧАС у нас есть утечка ресурсов. Когда исключение выдается из конструктора, объект никогда не создается полностью. Поскольку он никогда не создается полностью, его деструктор никогда не будет вызываться. Поэтому мы протекаем r.

...