четверг, октября 05, 2006

Microsoft Access. Что делать когда запросы не выполняются подряд

Начну с примечания, считаю, что Access - это наиболее удобное средство для разработки небольших и средних баз данных. Однако, и тут бывают свои подводные камни.

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

  1. SELECT ... INTO tabletmp1
  2. UPDATE tabletmp1 SET ...
    WHERE ... IN (SELECT ... FROM tabletmp1)

Запросы могут запускаться стандартными средствами VBA например после события нажатия кнопки на форме. В обработчике пишем следующее:

DoCmd.OpenQuery "Zapros1", acViewNormal, acEdit
DoCmd.OpenQuery "Zapros2", acViewNormal, acEdit

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

Что при этом может произойти. Запрос2 начнет выполняться, не дожидаясь полного завершения запроса1. А данные естественно, которые должен обновлять запрос2, еще не внесены (временная таблица может уже существовать в базе данных). Понять такую заморочку мне удалось не сразу. Было странно, что часть данных в результате выполнения макроса куда-то исчезала. Запустив отладчик, я был удивлен еще больше, когда все, что мне было нужно, выполнилось правильно в пошаговом режиме. Продолжая экспериментировать, мне в голову пришла та мысль, которую я озвучил выше, и я попытался ее проверить. Поставив временные задержки между выполнениями запросов, мне удалось избежать потери данных, однако суммарное время таких задержек превышало 15 минут.

Тогда я попытался обратиться к MSDN. Мне удалось найти команду более низкого уровня для выполнения запросов

CurrentDb.Execute "Zapros1"

Однако эта команда не поддерживает запросы на DELETE и SELECT ... INTO table, если такая таблица уже существует. Тогда я попытался написать так

DoCmd.DeleteObject acTable, "tabletmp1"
CurrentDb.Execute "Zapros1

Как вы думаете помогло? Правильно, нет. На этот раз у меня просто выскочила ошибка во время выполнения команды CurrentDB.Execute, что таблица в которую я хочу поместить данные этим запросом, существует. Но как, если я ее удаляю? Запустив отладчик, я был снова, но уже не так удивлен. На этот раз проблема выполнения одной команды, когда еще не завершилась другая, возникла между DoCmd.DeleteObject и CurrentDb.Execute.

Понять, как именно происходит распараллеливание задач Access'ом, мне так и не удалось. А вопрос, как его отключить, по-прежнему остается открытым.

Указатели как параметры функции. Динамическая память

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

char*str = new char[size];

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

void Realloc( char*s){
    delete s;
    s = new char[25];
    strcpy(s,"defgh");
}
void f()
{
    char*str = new char[20];
    strcpy(str,"abc");
    Realloc(str);
    delete str;
}

Давайте попытаемся проанализировать то, что здесь происходит. Сначала мы выделили динамически память под переменную str. Затем инициализировали ее, применив функцию strcpy. Дальше вызываем нашу функцию Realloc, причем заметьте строку мы передаем через указатель. Внутри функции происходит перевыделение памяти. Сначала мы освобождаем 20 байт старой памяти, потом выделяем 25 новой.

delete s;
s = new char[25];

Ничего криминального вроде бы не происходит. Переменная s соответствует переменной str.

Наконец после возврата из функции Realloc мы надеемся получить в переменной str строку defgh.

С этого момента и начинается интересное. То, что мы описали, будет работать под борландовскими компиляторами (тестировал на CBuilder6). А вот майкрософт иначе посмотрел на вещи (тестировал на Visual C++6). Если размер перевыделяемой памяти оставить прежним (бесмысленно - но для эксперимента), все будет работать. В нашем же примере размер изменился, и после выхода из функции Realloc в переменной str будет находиться всякая ерунда. Причину можно найти в отладчике - происходит смещение адреса указателя в памяти. Правда для меня остается загадкой, какое это вообще имеет отношение к изменениям в динамической памяти. Напоследок мы просто вылетаем из программы, что в общем уже и не удивительно, в момент выполнения

delete str;

среда, октября 04, 2006

Наследование в С++. Доступ к private-элементам

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

Член класса может быть закрытым (private), защищенным (protected) или открытым (public). Если он закрыт, его имя может использоваться только в функциях-членах и друзьях класса, в которых он объявлен.
Б.Страуструп - Язык программирования С++ - Бином - 2001г

Логично ли это? Позволю сделать себе такое предположение, что да. Пусть у нас есть класс B:

class B:{
public:
    B(){}
    void fun_set(char password,int _a){
         if(password=='1')
             set_a(_a);
     }
     int fun_get(char password){
         if(password=='1')
             return get_a();
         else return 0;
     }
private:
     void set_a(int _a){a = _a;};
     int get_a(){return a;}
     int a;
};

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

Усложним пример, пусть у нас есть следующая иерархия классов:

class A{
public:
     A(){}
     virtual void set_a(int _a){a = _a;};
     virtual int get_a(){return a;};
private:
     int a;
};
class B: public A{
public:
    B(){}
    void fun_set(char password,int _a){
         if(password=='1')
             set_a(_a);
     }
     int fun_get(char password){
         if(password=='1')
             return get_a();
         else return 0;
     }
private:
     void set_a(int _a){a = _a;};
     int get_a(){return a;}
     int a;
};

Казалось бы, что при этом изменилось? На первый взгляд, мы просто сказали, что наш класс B является наследником некоторого класса А, который содержит в себе обычные виртуальные функции. Да, эти функции имеют такие же имена, как и наши set_a и get_a и в отличии от них они открыты. Чувствуете подвох?

Если нет, тогда вам будет интересно проверить:

int main()
{
     B b1;
     b1.fun_set('1',15);
     A*a1 = &b1;
     a = a1->get_a();
     return 0;
}

Что произошло. Сначала мы создали объект класса B и обратились к его члену через функцию fun_set - ничего криминального. Потом мы создали указатель на базовый класс А и занесли в него ссылку на класс-наследник B. После этого мы вызываем функцию get_a через указатель на класс А.

И вот наконец мы добрались до сути. Как и следует принципу виртуальных функций, произошло переопределение, и мы попали в функцию get_a класса B. И это несмотря на то, что функция была закрытой (private).

Интересно, не правда ли? Сделано так - и ничего с этим не поделаешь, однако разработчики, ко всему прочему, еще и не учли этот момент в своей документации.

Главная новость дня

Вот оно, свершилось наконец-то!!!
Не знаю пока, на что это будет похоже - на "записки натуралиста" или что-то еще - посмотрим:)