KnigaRead.com/

Albert Makhmutov - Идиомы и стили С++

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Albert Makhmutov, "Идиомы и стили С++" бесплатно, без регистрации.
Перейти на страницу:

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

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

template ‹class T›

class MP {

private:

 T* t;

public:

 MP():t (new T) {}

 MP(const MP‹T›& mp):t(new T((*mp.t))) {}

 ~MP() {delete t;}

 MP‹T›& operator= (const MP‹T›& mp) {

  if (this != &mp) {

   delete t;

   t = new T(*(mp.t));

  }

  return *this;

 }

};

Чувствуете ли Вы, что идеология умных указателей близка к идеологии COM? Если еще нет, то готовьтесь - сходство явится самое ближайшее время. IUnknown, QueryInterface, ClassFactory и интерфейсы объектов - все полностью взято из идиоматики умных указателей.

Шаг 6 - Ведущие указатели. Еще пара слов.

Применение ведущих указателям таково: Вы можете изменять поведение указываемого класса без применения наследования и не изменяя его код, и его новое поведение не будет отражаться на субклассах. Не обязательно СУЩЕСТВЕННО изменять поведение. Можно вести статистику класса или объекта, или сделать объект "только на чтение", поставив модификаторы const на сам оператор -› и возвращаемое значение:

const T* operator-›() const;

И вовсе не надо изменять код класса. Это совсем немаловажно, если у Вас, к примеру, коммерческая библиотека классов.

А еще можно сделать умный указатель на ведущий указатель. Или ведущий указатель на ведущий указатель. Вам не стало еще плохо? Если нет, то alors, en route!

Шаг 7 - Интерфейсы. Интерфейсные указатели.

Извините, тут лирическое отступление. Если хотите пропустить - нажмите PageDown.

2001, март, 5 число. Вот уже седьмой шаг. Я ухлопал на эти шаги весь свой законный Курбан-Байрам, и не соблюдаю намаз. Одако не забываю добавлять коньяк в кофе, что придает определенный колорит… шагам. Остановлюсь на некоторое время. Посмотрим, что получится. Честно говоря, мне нужна обратная связь. Я не Толстой, и не Буч, и не совсем уверен, что эти шаги нужны человечеству. Поэтому я прошу Вас сообщить мне, насколько Вам интересны темы, затронутые мною, и если Вас это не затрудняет - несколько слов о том, кто Вы и какой Ваш опыт работы (я хочу выяснить, в какую аудиторию я попадаю, и куда ввязываюсь), буквально пару строк. У меня есть материал примерно еще на 20-30 шагов по идиоматике, а потом можно будет поковырять объектный анализ. Про анализ замечу: софт девелоперу за бугром предлагается 55-70 тонн баксов в год, а аналисту 90-120. Есть разница? Да и вообще, зачем разбирать идиоматику C++, если потом нарисовать диалоговое окно, положить в него одну кнопку, а на OnClick повесить обработчик одного сообщения, весом в 20 тысяч строк. Ну это личное дело каждого. Я сам так делаю. На дельфях и фокспре.

А можно еще потолковать о распределенных приложениях. Или архитектурных решениях. Блин, надо же подняться как-то над WinAPI и RAS, они же и в MSDN есть. Ну ладно, будет с этим. Конец лирического отступления. Вернемся к нашим баранам, то бишь указателям.

Давайте подумаем, какой интерфейс есть у класса. Его объявление? Верно. Но не все. Интерфейс - это то, что видит клиент. А видят разные клиенты разное. Отношения дружбы, наследования, модификаторы доступа сами по себе изменяют интерфейс. Как мы можем приобрести почти неограниченный контроль над интерфейсом? Ранее рассмотренные ведущие указатели не позволяют изменять интерфейс, ибо перегруженный оператор -› действительно позволяет осуществлять доступ к настоящему интерфейсу, а значит, информация о нем должна быть доступна. Ну и не будем его перегружать. Ничего не мешает просто скопировать нужную часть его интерфейса в определение указателя, и вызывать нужные функции объекта в одноименных функциях указателя. Если нас интересует секретность, то делаем все функции не-подстановочными (то есть определяем их вне объявления класса и без модификатора inline). Вот код:

// Это находится в заголовочном файле.

class Cthat;

class CPthat {

private:

 Cthat* t; // обычный указатель на объект

public:

 CPthat ();

 CPthat (const CPthat&);

 ~CPthat ();

 CPthat& operator=(const CPthat&);

 // Новый интерфейс - дубликат

 void funct1(void);

 void funct2(void);

};


// Это все содержится в cpp-файле.

// и первое - определение указываемого объекта

class Cthat {

 friend class CPthat;

private:

protected:

 Cthat();

public:

 // Родной интерфейс.

 void funct1(void);

 void funct2(void);

};


//Реализация членов-функций класса указателя.

CPthat::CPthat ():t(new Cthat) {}

CPthat::CPthat (const CPthat& _cp):t(new Cthat(*(_cp.t))) {}

CPthat::~CPthat (){ delete t; }

CPthat& CPthat::operator=(const CPthat& _cp) {

 if (this != &_cp) {

  delete t;

  t = new Cthat(*(_cp.t));

 }

 return *this;

}

void CPthat::funct1(void) { t-›funct1(); }

void CPthat::funct2(void) { t-›funct2(); }


// Реализация членов-функций класса объекта

Cthat::Cthat() {}

void Cthat::funct1(void) {}

void Cthat::funct2(void) {}

Все, приплыли. От класса указываемых объектов остался только перископ в виде class Cthat;, а более ничего. CPthat действует вместо него. Он сам стал им. Класс CPthat является классом интерфейсного указателя.

Теперь можете идти за пивом. Вы властелин вселенной. Вы можете превратить кого угодно во что угодно. Имея на вооружении идиомы умных, ведущих и интерфейсных указателей, сочетая их в любых комбинациях, Вы можете превратить в урода любой класс на выбор. Или в красавца. Ваше желание - закон, господин. Мне кажется, Вы уже знаете, о чем будет следующий шаг. Но я не могу подобрать нормального термина этому. Слово "интерфейс" заездили до последней степени. Скоро руль и педали назовут интерфейсом автомобиля к шоферу, а вилка, ложка и нож будут универсальным интерфейсом еды. И конечно, Мелкомякг именно это слово применил для следующей идеи. А еще это называют "suite" - комплект, или "facet" - грань. В общем, следующий шаг - о множественных интерфейсных указателях на объект.

Шаг 8 - Еще раз о статистике класса.

Пока праздники не начались и не кончились, пока работает интернет и библиотека, снова займемся любимым делом - статистикой класса. Мы уже говорили о ней, и уяснили, что здесь неплохо работает идиома ведущих указателей. Но на плюсах работает в течение 20 лет сотни тысяч программеров, и было бы неверно предположить, что они за это время не изобрели иных приличных способов реализовать статистику. Так… что же они там навыдумывали?

Мысль о классе, ответственном за статистику, приходит быстро. Да, это работает, но как только иерархия классов разрастается, увеличивается количество геморроя. Наследование становится множественным, потом виртуальным, статические члены сохраняются в классах мертвым грузом… ну в небольших иерархиях работает, но я не для этого взялся за шаг.

Изящную идиому предложил Коплин в 1995 году, а Мейерс развил ее в 2000. Правда, она предполагает вмешательство в код класса, но это не мешает быть очаровательно красивой. Основана она на том, что мы определяем шаблон класса счетчика и втыкаем его наблюдаемый класс статическим членом или наследованием. Шаблон расширяется ровно один раз для каждого класса, и это гарантирует нам наличие ровно одного экземпляра статистики. Код лучше объяснит.

// Это шаблон класса, ответственного за статистику.

template ‹class T›

class CCounter {

public:

 CCounter() { ++m_iCount; }

 CCounter(const CCounter&) { ++m_iCount; }

 ~CCounter() { --m_iCount; }

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*