в

Kazan Dev Alliance

Казанское Сообщество Разработчиков Программного Обеспечения

Персональный блог Александра Демченко

  • const_cast и устранение дублирования кода

    const_cast

    С точки зрения С++ если имеется некоторый тип T (без квалификаторов const или volatile), то тип const T (volatile T, const volatile T) является отдельным типом. Однако, во многих случаях можно использовать один вместо другого.

    Например:

    const T f()
    {
    return T(); // T не является const T
    }

    С другой стороны такая взаимозаменяемость не всегда возможна, например:

    int t = 12;
    const int& const_ref_t = t;
    int& ref_t = const_ref_t; // ошибка, присваивание не возможно

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

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

    Например, понятно, что во втором примере const_ref_t можно без опасения преобразовать к int&, поскольку объект, на который ссылается const_ref_t реально не является константой. В С++, как известно, за снятие и установление константности (или volatile) на объект отвечает оператор приведения const_cast. const_cast выглядит как шаблонная функция, параметром шаблона которой является тип, к которому осуществляется преобразование, а обычным параметром - объект, который нужно преобразовать. Вот как можно было бы использовать const_cast во втором примере:

    int t = 12;
    const int& const_ref_t = t;
    int& ref_t = const_cast<int&>(const_ref_t); //осуществляется явное преобразование
    //из const T& к T&.

    В результате такого преобразования, ref_t так же, как и const_ref_t ссылается на t. Поэтому ref_t++ влечет увеличение t на единицу.

    Несколько модифицируем пример, объявив t константой.

    const int t = 12;
    const int& const_ref_t = t;
    int& ref_t = const_cast<int&>(const_ref_t); //Снятие константности, ОК
    ref_t++; //Попытка изменить константу - неопределенное поведение.

    Приведение типов здесь пройдет успешно, но попытка модифицировать по полученной ссылке значение влечет неопределенное поведение, так как это по ссылке хранится дейтствительно константа.

     При этом поскольку поведение в указанной ситуации не определено, исполнив такой код, Вы не обязательно получите ошибку времени выполнения или какое-нибудь диагностическое сообщение. Я, например, откомпилировав этот код на компиляторе MS VS 2005 и запустив его, получил, что после ref_t++ значение константы не изменилось. При этом, конечно, поведение от запуска к запуску может меняться. В силу указанных причин такую ошибку сложно отловить в процессе отладки.

    Использование const_cast для устранения дублирования кода.

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

    class Array
    {
    int* pArray;
    int length;

    public:
    //Methods
    };

    Теперь нам хочется удобно обращаться к элементам массива - читать и изменять их. Поэтому мы решаем определить оператор []:

    int& operator[](int index)
    {
    if ( (index < 0) || index >= length)
    throw std::exception("Incorrect index");
    return pArray;
    }

    Теперь, имея такой оператор мы можем читать и записывать элементы:

    Array arr(12);
    int val_3 = arr[3]; //Читаем   
    arr[3] = 14; //Изменяем

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

    const Array arr(12);
    int val_3 = arr[3]; //Ошибка компиляции,
    //вызов
    неконстатного метода для константого объекта.

     

    Для того, чтобы позволить читать элементы констатного массива, мы вынуждены создать константный аналог оператора []:
    const int& operator[](int index) const
    {
    if ( (index < 0) || index >= length)
    throw std::exception("Incorrect index");
    return pArray;
    }

    Теперь все нормально. Если оператор [] вызывается для констатного объекта, то вызовется его константный вариант. При этом, попытка изменить элемент константного массива вызовет ошибку компиляции, так как будет попытка присвоить значение по константной ссылке.

    const Array arr(12);
    int val_3 = arr[3]; //ОК, вызывается operator[] const
    arr[3] = 12; //Ошибка компиляции - вызывается operator[] const,
    //но
    по константной ссылке, которую он возвращает   нельзя менять объект

     Итак, окончательно имеем класс:

    class Array
    {
    int* pArray;
    int length;

    public:
    int& operator[](int index)
    {
    if ( (index < 0) || index >= length)
    throw std::exception("Incorrect index");
    return pArray[index];
    }
    const int& operator[](int index) const
    {
    if ( (index < 0) || index >= length)
    throw std::exception("Incorrect index");
    return pArray[index];
    }
    };

     Здесь мы конечно замечаем известный антипаттерн Copy and Paste.  Одну и ту же функциональность нам пришлось просто скопировать и вставить в константный аналога оператора []. Если здесь это не так много - всего 3 строчки, то, конечно, в реальных ситуациях, этот общий код может быть гораздо больше.

     Конечно, можно попробовать избавиться от Copy and Paste, например, просто вызвав из константного метода неконстантный. Так конечно сделать нельзя, поскольку неконстантный метод может изменять объект и компилятор это понимает. Однако, никакого вреда не будет, если мы из неконстантного метода вызовем констатный.

    Код мог бы выглядеть примерно так:

    int& operator[](int index)
    {
    const int& ref_elem = /* call operator [] const */
    return const_cast<int&>(ref_elem); //Безопасно,
    //так
    как реально объект не является константой
    }

    Перед возвратом из функции нужно преобразовать полученную константную ссылку к неконстантной, поскольку мы планируем, что посредством неконстантного оператора [] пользователь может захотеть изменить элемент массива.

    Вопрос здесь в том как вызвать именно константный метод. Прямого способа указать компилятору, что мы хотим вызвать именно этот метод, нет. Однако, он сам это сделает, если увидит, что вызов [] применен к константному объекту. В таком случае, самое время вспомнить о const_cast и "навесить" константность на this. Итоговый код будет таким:

    int& operator[](int index)
    {
    return const_cast<int&>( const_cast<const Array*>(this)   -> operator[index] );
    }
     
     
    Дополнительные материалы:
    Операторы преобразования С++
    Константные функции-члены
    Антипаттерны
    Книга Скотта Мэйерса "Эффективное использование С++. 55 верных советов улучшить структуру и код
    ваших программ". Правило 3 (Как избежать дублирования в константных и неконстантных функциях-членах)
  • Questions from C++ examine

    В зимнем семестре мне довелось в КГУ читать спец. курс по С++. Предполагалось, что студенты ранее могли не изучать C или С++.

    В зимнем семестре удалось рассмотреть базовые конструкции языка и классы. Шаблоны были перенесены на весенний семестр. Также одна лекция была посвящена макросам.

    Программу зимнего семестра можно скачать тут.

     Экзамен делилился на 2 части. Первая и главная часть - практическая. Нужно было реализовать класс и протестировать его. Это задание было в двух вариантах. Первым вариантом было реализовать класс DynamicArray - массив, при необходимости расширяющий свои размеры (похожий на std::vector). Вторым вариантом - реализовать класс String (строка). Практическое задание было достаточно подробно описано. Было небольшое preview, в котором описывались проблемы, которые разрабатываемый класс должен решить, был приведен интерфейс класса и, наконец, были приведены некоторые инварианты, которым класс должен удовлетворять. Последнее, в частности, должно было снизить вероятность возникновения спорных вопросов, когда, к примеру, я считаю, что некоторая функциональность работает неверно, а студент считает, что верно.

    Практическое задание (C++) - вариант 1 (PDF скачать)

    Практическое задание (C++) - вариант 2 (PDF скачать)

     

    Второй маленькой частью экзамена были простенькие (где-то даже забавные) вопросы по С++, которые не требуют большого исследования. Размещаю эти вопросы и жду ответов в комментах ;-)

    1. Пусть задан некоторый встроенный тип Т. Всегда ли результат sizeof(T) зависит от реализации?

    2. Не используя инструкций выбора напишите функцию Round, которая принимает аргумент типа float и возвращает целое, являющееся результатом округления аргумента. Гарантируется, что аргумент
      неотрицателен.

      Правила округления обычные: если дробная часть меньше 0.5, то выбирается наибольшее целое, не превышающее аргумента. В противном случае -  наименьшее целое, которое не меньше аргумента.

    3. Приведите примеры (>1), когда отсутствие инициализатора в объявлении вызовет ошибку компиляции.

    4. Имеется следующий код:
         
          char* pString = "C++";
          char val = *(pString + 3);
          char* p = pString + 4;
          pString[0] = 'D';

      Определено ли поведение во второй, третьей и четвертой строках листинга. Если да, то каков результат их выполнения, а именно, что будет содержаться в val после выполнения второй строки, в p после
      выполнения третьей и в pString[0] после выполнения четвертой строки.

    5. Здесь требуется некоторый комментарий. В течение семестра мы не рассматривали многомерные массивы и в этом задании студентам предлагается догадаться до того, как это можно сделать в C++ самостоятельно.

      Для данного типа T тип T* является "указателем на T". То есть переменная типа T* может хранить адрес объекта типа T. Из этого определения следует, что если в качестве типа T взять T*, то переменная типа T** является указателем на T* и может хранить адрес переменной типа T*.
      Рассмотрим это на примере:
         int main()
         {
             int x = 1;
             int* pX = &x;
             int** ppX = &pX;
             return 0;
         }

      Переменная ppX имеет тип "указатель на указатель на целое" и, следовательно, может хранить адрес объекта, являющегося указателем на целое.

      Выделение памяти под массив указателей на T (по аналогии) происходит с помощью операции new T*[], тип возвращаемого значения такой операции --- T**.
      Двумерный массив можно считать одномерным массивом, каждый элемент которого сам является массивом.

      Задача. Используя рассмотренные сведения, написать код выделения памяти под двумерный целочисленный массив из m строк и n столбцов (m и n неизвестны на момент компиляции).

      Написать код освобождения памяти для полученного массива.

    6. Дан следующий код:

         #include<iostream>
         using namespace std;
         enum Number
         {
            One = 1,
            Two,
            Three
         };

         int main()
         {
            Number a = ...;
            switch(a)
            {
               case One:
                 cout<<"One";
                 break;
               case Two:
                 cout<<"Two";
               case Three:
                 cout<<"Three";
                 break;
               default:
                 cout<<"Don't know";
                 break;
          }
          return 0;
        }
      Строка Numbers a = ...; - это псевдокод. а инициализируется некоторым значением. Что выведется на экран если a равно One, Two, Three? Переписать приведенный код с использованием инструкции выбора if вместо switch.

    7. Напишите функции PrefixIncrement и PostfixIncrement которые увеличивают на единицу значение целого числа, переданного в качестве параметра. Обеспечьте, чтобы первая функция имела поведение аналогичное префиксному инкременту, а вторая - постфиксному.

    8. Почему следующий код вызовет ошибку компиляции:
         
          int x = 1;
          int* p = &x++;

      Скомпилируется ли такой код:
         
          int x = 1;
          int* p = &(++x);

    9. Почему следующий код вызовет неопределенное поведение:

          int* pInt = new int(12);
          cout<<*pInt;
          delete[] pInt;

    10. Сколько раз выполнится следующий цикл

          for (int i = 10; i--; i > 10)
          {
              //Do something
          }
       
      Свой ответ объяснить.
      Подсказка. Не торопитесь с ответом.

    11. Имеется следующий код:

         class A
         {
             A(const A& copy)
             {
                 //Do something
             }
           public:
             A() {}
         };

        
      class B
         {
             A a1;
             A a2;
         };

      Все ли конструкторы, которые при необходимости генерируются компилятором неявно, могут быть им корректно созданы в случае с классом B.
      В частности, будет ли код компилироваться, если к указанному коду добавить main:


         int main()
         {
             B b;
             B c = b;
             return 0;
         }

    12. Пусть имеется некоторый пользовательский бинарный оператор, определенный в виде  глобальной функции. Как гарантировать, чтобы первый его операнд был модифицируемым(неконстантным) lvalue? (Гарантировать в том смысле, что если это не так, то возникнет ошибка компиляции). Как гарантировать, что его первый операнд
      может быть константным объектом? Как это гарантировать, если оператор определен в виде метода класса?

    13. Имеется следующий код:
         class A{ };

         class B : protected A { };

         class C : public B
         {
         public:
            void SomeMethod()
            {
               A* pA = this;
            }
         };

         int main()
         {
             B b;
             A* pA = &b;
             return 0;
         }

      Какие строки этого кода вызовут ошибку компиляции и почему?

      Изменилась ли бы ситуация и, если да, то как, если при наследовании B от A использовалось бы закрытое, открытое наследование?

    14. Имеется следующий код:
         class A
         {
         protected:
             int val;
         public:
             A(int val)
             {
                 this->val = val;
             }
         };

        
      class B : A
         {
         public:
             B()
             {
                 val = 3;
             }
         };

      Почему код не компилируется при таким образом определенном конструкторе класса B. Измените конструктор B так, чтобы код успешно компилировался.

      Будет ли код компилироваться и почему, если конструктор B определить следующим образом:

         class B : A
         {
         public:
             B() : val(3) { }
         };

     

    Скачать вопросы в PDF

  • С Великим Днем Победы!

    Поздравляю всех С Великим Днем Победы!

    Это, пожалуй, один из немногих настоящих праздников!

  • О нас написали...

    О начале нового дела студентами ВМК написали в мартовском номере за 2007 год газеты "Казанский Университет". Примечательно, что все трое являются участниками itkazan.com.
    Сама статья в прикрепленном файле.

  • Самые оригинальные гаджеты марта

    На сайте dp.ru опубликована статья с описанием самых оригинальных по мнению авторов гаджетов марта. Интересен подход авторов, которые пытаются понять, собственно, необходимость и применимость таких гаджетов в хозяйстве. В общем весьма веселая и легкая для чтения статья.
  • Интервью c Ольгой Дергуновой

    Журнал "Эксперт" провел онлайн-интервью с Ольгой Дергуновой. Как говорится в анонсе, Ольга Дергунова
    рассказала как и почему Microsoft борется с пиратами, как защитить детей от порнографии в интернете, может ли в России появиться свой Билл Гейтс, и почему в семье Ольги 503 человека.
    По моему, для IT-менеджеров интервью не будет особо интересным, они и так ориентируются в рассматриваемых вопросах. Интервью скорее получилось ориентированным на частных пользователей. Кроме этого не все вопросы были интересными, а некоторые и того более - странными, вот, например Big Smile:
    13. Почему правоохранительные органы, которые живут на средства налогоплательщиков, за деньги граждан РФ должны обеспечивать доход западным компаниям, в частности Microsoft?

    Был вопрос, в общем-то обычный для интервью с IT-менеджерами:

    Что вы думаете о перспективах программных продуктов с открытым кодом? Не кажется ли вам, что будущее именно за ПО такого типа?


    Однако, мне очень понравился ответ Ольги Дергуновой. Я считаю, что он очень правильный. Она сказала, что будущее за теми, кто удержит технологическое лидерство. Отсюда следует, что вряд ли стоит вести ожесточенные споры по поводу OpenSource и проприетарного ПО в принципе. Все должно определяться теми возможностями, которое то или иное ПО предлагает клиентам продукта.
  • 6 марта вышел релиз Microsoft Visual Studio 2005 Service Pack 1 (SP1) Update for Windows Vista

    В общем-то уже запоздалое сообщение Smile. Здесь можно посмотреть сессию вопросов ответов S. Somasegar. Corporate Vice President, Microsoft Developer Division.

    Вот ссылка на список 398 багов, устранненных в VS 2005 SP1. Описания багов были получены от пользователей VS2005.

  • Top 200 российских деловых женщин

    Журнал Карьера опубликовал рейтинг российских деловых женщин. Оценки проводились экспертами по 11 критериям. Подробнее о критериях и, собственно, сам топ 200 можно посмотреть здесь. Интересно отметить, что Ольга Дергунова занимает 20 место, в 2005 и 2006 году она была первой. Наталья Касперская (генеральный директор  Лаборатории Касперского) занимает в рейтинге 7 место, как и в 2006 году (10 в 2005). на 90 месте находится Людмила Фатеева - директор департамента маркетинга Майкрософт Рус, так что русский офис Майкрософт представлен сразу двумя женщинами.
  • Дело Поносова закрыто...

    Наверное все уже слышали о деле против директора сельской школы, которого обвиняли в использовании нелицензионных копий программ Microsoft в школьных классах. Так вот, "Верещагинский районный суд Пермского края закрыл нашумевшее дело о пиратстве сельского учителя, признав его "малозначительным"".Источник

    Интересно, что сам Поносов собирается обжаловать решение суда, так как не согласен с формулировкой постановления: "Получается, что это оправдание не из-за отсутствия состава преступления, а потому, что ущерб для корпорации Microsoft - это семечки".
    Надо сказать, что это дело получило в обществе серьезный резонанс. Об этом деле журналисты уже успели спросить Президента, Михаил Горбачев написал открытое письмо в Microsoft с просьбой отозвать претензии к Поносову, Microsoft ответила на это письмо, ну и другие известные люди выступили на стороне Поносова, такие как, например, Анатолий Кучерена - юрист, один из членов общественной палаты. Роспечать, кстати, даже пообещала компенсировать нанесенный Microsoft ущерб за  счет своих внебюджетных средств.

    Интересен ответ Microsoft на письмо Горбачева:
    "Важно подчеркнуть, что Microsoft не инициировал уголовного преследования в отношении г-на Поносова, уголовное дело было возбуждено российскими властями и в соответствии с российским законодательством. Нам впервые стало известно об этом деле несколько месяцев назад, и, оценив обстоятельства дела, мы приняли решение не заявлять даже гражданского иска против г-на Поносова. В целом, мы не считаем, что дела подобного рода обязательно должны рассматриваться в уголовном порядке, учитывая малое число компьютеров и то, что они были приобретены для использования школьниками.
    ......
    В подобного рода делах, когда мы сталкиваемся с использованием пиратского программного обеспечения в школах, Microsoft готов работать с местными Департаментами образования, чтобы помочь им решить проблему и лицензировать используемое программное обеспечение без необходимости привлечения юридических средств защиты прав."

    Интересно, есть ли у Microsoft какие-нибудь специальные недорогие подписки на продукты для школ, как, например, для вузов MSDN Academic Alliance. По крайней мере, я думаю, это было бы в их стиле. Да, и, вообще, наверное, Microsoft могла бы разрешить использование своих продуктов в школе, если бы у нее спросили.
    С другой стороны, если нет денег и не получается договориться, то так уж ли нужны в процессе обучения дорогие продукты Microsoft. Например, в процессе обучения без всякого  вреда можно заменить дорогой Office на опенсорсный OpenOffice. А операционную систему покупать вместе с компьютерами (OEM), что в несколько раз дешевле. Кстати, некоторые попытки заменить дорогие продукты на бесплатные в школах произошли на днях в Перми. Акция называлась "Директор может спать спокойно"Big Smile. В ее рамках Пермская гражданская палата и Общество развития предпринимательских инициатив — предлагают "современный и дешевый способ избежать «лицензионного скандала» — использовать OpenOffice как альтернативу Microsoft Office". Впрочем, почитайте сами.

© 2007 Kazan Developers Community and Post`s Authors