×
Namespaces

Variants
Actions

Fundamentals of Symbian C++/Object Construction/ru

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata

Статья
Перевод:
hamishwillee
Последнее редактирование: hamishwillee (09 Dec 2011)

В этой статье рассказывается про шаблон двухфазного конструктора в Symbian C++, который обычно используется для создания объектов на куче.

Contents

Двухфазное Построение

Рассмотрим следующий код, который размещает объект типа CExample на куче и присваивает его к переменной foo:

CExample* foo = new(ELeave) CExample();

Код вызывает оператор new, который выделяет на куче место под объект типа CExample если хватает свободной памяти. После этого он вызывает конструктор класса CExample для инициализации объекта. Если конструктор CExample сбросит (leave), память, выделенная для foo и любая дополнительная память, которую мог выделить конструктор, "осиротеет", так как адрес объекта CExample не присвоен переменной foo после построения. Чтобы этого избежать, вот ключевое правило управления памятью в Symbian C++:

Ни одна функция внутри C++ конструктора не должна сбрасывать (leave).

(Более детально это правило описано здесь)

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

Двухфазный конструктор разбивает построение объекта на две части или фазы:

1. Приватный конструктор, который не может сбросить (cannot leave).

Это тот конструктор, который вызывается оператором new. Он неявно вызывает конструктор базового класса и так же может вызывать функции, которые не могут сбросить, и/или инициализировать поля-переменные значениями по умолчанию или теми, которые поставлялись конструктору как аргументы.


2. Приватный метод класса (обычно названый ConstructL()).


Этот метод вызывается отдельно после того, как объект, построенный и размещенный в памяти оператором new, был поставлен в стек очистки (cleanup stack); он завершит инициализацию объекта и может безопасно вызывать функции, которые могут сбросить (leave). Если будет сброс, стек очистки вызовет деструктор, который освободит ресурсы, уже успешно размещенные в памяти, и уничтожит память, выделенную для самого объекта.

Класс предоставляет статическую функцию-обертку для обеих фаз конструктора, являющуюся простым и легко узнаваемым способом создания экземпляров этого класса. Эта функция обычно назвается NewL() и является статической, чтобы ее можно было вызывать без существующего экземпляра класса. Несбрасывающий конструктор и функция второй фазы ConstructL() приватны, чтобы их случайно не вызвать извне класса или чтобы экземпляры класса не создавались другим путем, кроме как через NewL(). Например:

class CExample : public CBase
{
public:
static CExample* NewL();
static CExample* NewLC();
~CExample(); // Должен бороться с частично созданными объектами.
...
private:
CExample(); // Гарантированно не сбрасывает.
void ConstructL(); // Код второй фазы построения, может сбрасывать.
CPointer* iPointer;
};

Обратите внимание, что в классе CExample есть еще функция NewLC(). Если указатель на объект заносится в стек очистки и после завершения функции остается там же, то по конвенции имен в Symbian C++ к имени этой функции добавляется суффикс C. Это указывает вызывающему методу на то, что, если вызванная функция завершилась без ошибок, то в стеке очистки будут дополнительные указатели.

Типичная реализация функций NewL() и NewLC() выглядит так:

CExample* CExample::NewLC()
{
CExample* self = new (ELeave) CExample(); // Первая фаза построения.
CleanupStack::PushL(self);
self ->ConstructL(); // Вторая фаза построения.
return (self);
}
 
CExample* CExample::NewL()
{
CExample* self = CExample::NewLC();
CleanupStack::Pop(self);
return (self);
}

Функция NewL() реализована через NewLC() а не наоборот (что несколько менее эффективно, потому что это потребует дополнительного вызова PushL())

Каждая из этих функций возвращает полностью построенны объект или сбросит, если памяти недостаточно для размещения объекта (то есть, если сбросит оператор new(ELeave)), или если функция второй фазы ConstructL() сбросит по той или иной причине. Это значит, что если объект полностью инициализировать двухфазным конструктором, класс можно реализовывать без нужды в проверке каждого поля-переменной, чтобы понять, годен ли объект к использованию. Если объект существует, то он полностью построен.

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

Деструкция Объекта

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

CExample::~CExample()
{
if (iPointer) // iPointer may be NULL.
{
iPointer->DoSomething();
delete iPointer;
}
}

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

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

Чтобы этого избежать, когда член-переменная удаляется, обычный прием программирования - это установить указатель на ноль до того, как новый объект заменит старый. Если размещение нового объекта будет неудачным, по крайней мере деструктор, который вызовется позднее, не будет пытаться удалить изначальный объект заново. Для CExample:

void CExample::RenewMemberL()
{
delete iPointer; // Destroy old member variable.
iPointer = NULL;
iPointer = CDerivedPointer::NewL(); // Create a new one.
...
}

Однако, это влечет за собой проблему при реализации CExample, так как, если RenewMemberL() сбросит, поле iPointer будет равно NULL. Если RenewMemberL() метод, помеченный как public, класс не может гарантировать, что iPointer указывает на действительный объект, так как он может быть удален, но создать ему замену может не удасться. Это означает, что перед использованием iPointer нужно проверить на действительность, что сводит на нет пользу от двухфазного конструктора, описанного выше, то есть то, что все поля-указатели правильные, если объект существует.

Решение - написать RenewMemberL() с возможностью отката:

void CExample::RenewMemberL()
{
CPointer* tempPtr = CDerivedPointer::NewL(); // Создать новый указатель.
delete iPointer; // Удалить старое поле-указатель.
iPointer = tempPtr;
...
}

Если выделение памяти для нового объекта будет неудачным, RenewMemberL() сбросит, но оригинальный iPointer сохранится. Функция, вызвавшая RenewMemberL() получит ошибку и может выбрать: либо продолжить использовать оригинальный объект, либо обработать ошибку.

(Заметьте, что нет необходимости присваивать членам-переменным значение NULL, если удаление происходит в самом деструкторе).

Так как оператор new C классов обнуляет память, поле-указатель всегда будет или нулевым, или указывающим на действительный объект (при условии использования таких идиом, как RenewMemberL(), описанной выше).

Объекты, размещенные в стеке

В Symbian OS начиная с версии v9.1 объектам, размещенным в стеке (например T класса), стало возможным иметь деструктор, который вызывается при сбросе.

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

Оба случая рассмотрены в теме The Implications of Leaving in a Destructor (Последствия сброса в деструкторе).


Licence icon cc-by-sa 3.0-88x31.png© 2010 Symbian Foundation Limited. This document is licensed under the Creative Commons Attribution-Share Alike 2.0 license. See http://creativecommons.org/licenses/by-sa/2.0/legalcode for the full terms of the license.
Note that this content was originally hosted on the Symbian Foundation developer wiki.

This page was last modified on 9 December 2011, at 02:58.
81 page views in the last 30 days.
×