6/15/2005

  почему я не люблю мак… - (General- C++) 4:39 pm

Оказалось, что ежели исключение бросить в shared library то и ловить его нужно там же….. Среда - CodeWarrior. Может конечно и не в маке дело.

6/9/2005

  “Пляски с бубном” - часть вторая. - (C++) 10:58 am

Остановился, напомню, я на том, что такой достаточно простой кусок кода

CString* pStr = new CString; delete pStr;

приводил к порче памяти.

Резонный вопрос - а все ли в порядке с самим классом CString? Нужно сказать что этот класс делался по образу и подобию класса CString из MFC, проще говоря делался методом COPY/PASTE.

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

Тут я  решил проверить происходит ли падение если динамически выделять другие классы, определенные в этой библиотеке. Результат оказался предсказуемым - нет, с другими классами падение не происходит. Беглый просмотр принципиальной разницы между другими классами и CString не обнаружил.

Тупик. Тупичок. Разница естественно есть, просто я ее еще не вижу… Я создал маленький тестовый класс CStringTest. Добавил в него несколько произвольных полей. Падение прекратилось. Удалил поля, перенес все поля из CString (там на самом деле только одно) - падение повторилось. Хм… Закрались странные подозрения. Но проверка их только утвердила. Если sizeof(any_class) < 8 выделать класс динамически нельзя….

конец. Дальше я пытался исправить класс CString, сделав так чтобы sizeof(CString) оказался >= 8 байтам, оказалось что этого сделать невозможно, я наконец-то разобрался почему CString с подсчетом ссылок устроен именно так как он устроен и почему нельзя сделать по другому. Об этом в другой раз.

6/8/2005

  Работа программиста - “пляски с бубном". - (C++) 3:04 pm

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

Попробую описать один рабочий момент. Как мне кажется является хорошей иллюстрацией этих самых плясок.

Вступление - работаем под MacOS X, CodeWarrior. Итак, запускаем программу - программа падает. Падает при работе с вектором, точнее при добавлении элемента в вектор. Почему? Возможно моего менее опытного коллеги в этот момент начинают донимать мысли о суициде. Программа просто падает, ошибки в функциях нет… Но с опытом иногда приходит и знание - подобное падение есть сигнал к тому что память была разрушена гораздо раньше. Начинаем искать… Суть поискаа проста - пишется кусок кода активно работающий с памятью и начинает расставляться по стеку через каждую строчку которая в принципе могла обратиться к памяти. Скажу сразу - это дало довольно мало. Т.е. я почти подошел к месту где действительно ломалась память, но явно обнаружить таким образом место не смог. В этом случае принимаются более екстренные меры - кусок кода, активно работающий с памятью, начинает вставляться после каждой сколько-нибудь значимой строчки кода. Некоторое время спустя проблемный участок был обнаружен. Выглядел он вот так:

CStringTokenizer tRecVersion(sRecVersion, “.");

При этом sRecVersion - это строка вида “1.1″.

Без этой строчки не падает. С ней - падает… Начинаем лезть внутрь.

Глянув на реализацию класса CStringTokenizer я увидел много непонятного и переписал его за несколько минут.

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

vector<CString*>

использовать

vector<CString>

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

Последние предположение оказалось верным. Действительно, вот такие строчки

CString* pStr = new CString; delete pStr;

приводили к порче памяти.

продолжение следует.

10/10/2004

  C++ Evolution Working Group - (C++) 11:21 am

C++ Evolution Working Group – Active Proposals, Revision 1

Очень интересный документ, набор различным предложений по дополнениям и модификациям в C++. Большая часть разумеется касается шаблонов. Но есть и полезные :).

Кстати есть предложения об “опциональном” GC (сборщик муссора) и о “свойствах” (aka properties). А также о finally. :) 

А также о возможности использовать строки в операторе switch.

Есть и такие предложения, которые очень хотелось бы видеть:

  •  Implicitly-Callable Functions in C++
  • A Proposal to Make Pointers to Members Callable
  • Design-by-Contract facilities
  • for each
  • и другие….

Implicitly-Callable Functions in C++

class Square {
public:
explicit Square( double s = 0.0 ) : side_(s) { }
double side() const implicit { return side_; }
double area() const implicit { return side_ * side_; }
// …
private:
double side_; // length in cm
};
Square my_square( 4.0 ); // a 4x4 square
//…
cout << my_square.side << ’t’ << my_square.area;

Обратите внимание на вызов my_square.side. Мне такие вызовы кажутся более читабельными.

A Proposal to Make Pointers to Members Callable

Речь идет об упрощенном синтаксисе вызова функций в для итераторов с стандартных алгоритмах.
int main()
{
    std::vector<X> v;
    std::for_each( v.begin(), v.end(), std::mem_fun_ref(&X::f) );

    std::vector<X*> v;
    std::for_each( v.begin(), v.end(), std::mem_fun(&X::f) );
}

Подобные вызовы кажутся автору этого предложения (Peter Dimov) слишком сложными. Вот что он предлагает
std::for_each( v.begin(), v.end(), &X::f );

Лично я думаю что вот так вот:

std::for_each( v.begin(), v.end(),X::f );

было бы еще проще!.

Design-by-Contract facilities

Речь идет о явном новом блоке, предназначенном для валидации параметров.

Также еще вот ссылка один интересный документ.

  определения наличия функции-члена - (C++) 10:31 am

Взято как водится с RSDN, вот ссылка

#include <list>

namespace omni{

template <class T, T val>
struct member_wrapper{};

template <class T>
char test_for_swap(const T&, member_wrapper<void (T::*)(T&), &T::swap>* p = 0);

template <class T>
double test_for_swap(const T&, …);

template <class T>
struct has_member_swap
{
    static T t;
    static const bool value = (1 == sizeof(test_for_swap(t)));
};

//BOOST_STATIC_ASSERT(has_member_swap<std::list<int>::value);
//BOOST_STATIC_ASSERT(!has_member_swap<int>::value);
//BOOST_STATIC_ASSERT(!has_member_swap<noswap>::value);

template<bool b>
struct swapper
{
    template <class T>
    static void swap(T& t1, T& t2)
    { std::swap(t1, t2); }
};

template<>
struct swapper<true>
{
    template <class T>
    static void swap(T& t1, T& t2)
    { t1.swap(t2); }
};

template <class T>
void swap(T& t1, T& t2)
{
    swapper<has_member_swap<T>::value>::swap(t1, t2);
}

} // namespace omni

struct noswap{};

int main()
{
    std::list<intl1, l2;
    omni::swap(l1, l2);
    noswap s1, s2;
    omni::swap(s1, s2);
    int i1, i2;
    omni::swap(i1, i2);
    return 0;
}

Используется фича компиляторов “Substitution Failure Is Not An Error” (SFINAE). При разборе идеи следует обратить внимание на разницу в типах возвращаемых значений версий шаблона test_for_swap. Он тут самый главный.

9/24/2004

  Интересный способ перегрузки оператора "запятая" - (C++) 10:19 am

Взято с RSDN, вот отсюда

template<class T_numtype, class T_iterator>
class ListInitializer
{
public:
    ListInitializer(T_iterator iter) : iter_(iter) { }
    ListInitializer<T_NUMTYPE, T_iterator> operator,(T_numtype x)
    {
        *iter_ = x;
        return ListInitializer<T_NUMTYPE,T_ITERATOR>(iter_ + 1);
    }
private:
    ListInitializer();
protected:
    T_iterator iter_;
};
class Array
{
public:
    Array(std::size_t size = 10) : data_(new double[size])
    {
    }
    ~Array()
    {
        delete [] data_;
    }
    ListInitializer<DOUBLE,DOUBLE*> operator=(double x)
    {
        data_[0] = x;
        return ListInitializer<DOUBLE,DOUBLE*>(data_ + 1);
    }
private:
    double* data_;
};
int main()
{
    Array A(5);
    A = 0.1, 0.2, 0.3, 0.4, 0.5; // comma initialization list
}

Также из этой темы узнал, что оператор запятая - единственная “функция", которая может синтаксически принимать аргумент типа void.

3.9.1 9

The void type has an empty set of values. The void type is an incomplete type that cannot be completed.It is used as the return type for functions that do not return a value. Any expression can be explicitly converted to type cv void (5.4). An expression of type void shall be used only as an expression statement (6.2), as an operand of a comma expression (5.18), as a second or third operand of ?: (5.16), as the operand of typeid, or as the expression in a return statement (6.6.3) for a function with the return type void.

Также в этой теме описаны способы определения существования перегруженной функции с определенным параметром.

struct no_suitable_overload {};
no_suitable_overload my_func(…);
typedef char yes;
typedef char(&no)[2];
// Похоже оператор запятая - единственная “функция", которая может синтаксически принимать аргумент типа void
// и при этом может быть перегружена. Этим и воспользуемся.

no operator,(no_suitable_overload, yes);
// T2 operator,(T1, T2); // “обычная перегрузка” запятой, в нашем случае T2 == yes, а T1 может быть void
template<class T>
struct has_my_func
{
enum { value = sizeof((my_func((T*)0), yes())) == sizeof(yes) };
};

С ценными замечаниями об особенностях работы компилятора при поиске перегруженных функции и инициализации фаблонов.

Но есть еще другое. Для comeau имеет значение, объявлена перегрузка до или после определения шаблона:
struct no_suitable_overload {};
no_suitable_overload my_func(…);
typedef char yes;
typedef char(&no)[2];
no operator,(no_suitable_overload, yes);

void my_func(int*); // эту перегрузку comeau находит

template<class T>
struct has_my_func
{
    enum { value = sizeof(my_func((T*)0), yes()) == sizeof(yes) };
};

void my_func(short*); // а эту не находит

char a[has_my_func<int>::value];
char b[has_my_func<short>::value]; // ошибка

А VC7.1 находит обе перегрузки. Похоже что my_func в шаблоне является независимым именем и их вызовы должны связываться при первичном разборе шаблона. У VC есть специальный баг на этот счет: http://msdn.microsoft.com/library/en-us/vclang/html/vclrf1462dependentnames.asp.

Гуру, это так? Как можно считать my_func независимым именем, если выбор перегрузки зависит от параметра шаблона?

Нельзя.

Компилятор должен осуществлять two-phace lookup для неквалифицированных зависимых имен. Первая фаза осуществляется в точке определения шаблона, вторая в точке инстанцированния. На второй фазе осуществляется только ADL. Набор ассоциированных с short имен пустой, поэтому компилятор не должен найти void my_func(short*), что Comeau и делает.

Но, two-phase lookup осуществляют только EDG-based compilers, MetroWerks, и будущий gcc 3.4. Microsoft обещает добавить в следующей версии. Те компиляторы, которые не делают two-phase lookup, связывают имена в точке инстанциирования, поэтому MS находит обе перегрузки.

Что не приятно, это то, что переход с компилятора не поддерживающего two-phase lookup, на компилятор с оным, в случае как этот, может в лучшем случае поломать в код, а в худшем тихонько скомпилироваться, использовав другую версию ф-ции для аргумента встроенного типа.