Дружественные шаблонные операторы

Как человек, довольно редко использующий шаблоны и метаопрограммирование, периодически путаюсь них. Недавно был сильно озадачен, пытаясь написать оператор << для шаблонного класса. Хотелось получить поддержку следующего синтаксиса:

Buffer<int> buff;
int data1 = 100;
int data2 = 200;

buff << data1 << data2;


Казалось бы ничего сложного (да и без казалось ничего сложного), тем не менее пришлось подумать. Основная проблема – синтаксис который позволял использовать компилятор из вендовой студии не сработал:

template<typename T>
class Buffer
{
public:
friend Buffer<T>& operator<< ( Buffer<T> buff, const T& data );

Buffer()
{}
~Buffer()
{}
};

template<typename T>
Buffer<T>& operator<<( Buffer<T>& buff, const T& data )
{
// do smth
return buff;
}

Погуглив, я понял, что эта проблема волнует не только меня, но врет решения предлагались несколько кривоваты, например можно сделать так:

template<typename U>
friend Buffer& operator<< ( Buffer& buff, const U& data );

Но это не то что требуется. Хотя на самом деле, все просто и логично:

template<typename T>
class Buffer;

template<typename T>
Buffer<T>& operator<<( Buffer<T>& buff, const T& data );

template<typename T>
class Buffer
{
public:
friend Buffer<T>& operator<< <T> ( Buffer<T>& buff, const T& data );

Buffer()
{}
~Buffer()
{}
};

template<typename T>
Buffer<T>& operator<<( Buffer<T>& buff, const T& data )
{
// do smth
return buff;
}

Автоопределение прокси

Казалось бы, тривиальнейшая задача – определить настройки прокси сервиса, заданные при помощи .pac файла либо полученные в результате работы автоконфигурации. А в действительности оказывается что информации в интернете кот наплакал, а та что есть посвящена венде (что еще печальнее). Чтобы исправить эту жутко не справедливую ситуацию, я опишу как можно определить настройки прокси в Mac OS X приложении.
Итак, необходимый API появился в Mac OS X начиная с версии 10.5, так же API присутствует в iOS, но деталей по версиям я уже сказать не могу. Continue reading

printf и CoreFoudation

На мой взгляд, CoreFoudation очень интересная библиотека. Я бы сказал это лучший вариант реализации объектно ориентированной модели при разработке на Си, который я когда-либо видел. Но иногда возникают досадные мелочи, например невозможность использовать printf для стандартных объектов из CoreFoudation, а как было бы здорово.
Но, все не так уж и плохо, хотя и в гугле ответ находится не сразу:

#include <CoreFoundation/CoreFoundation.h>

int main (int argc, const char * argv[])
{
    CFStringRef outStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("Output string: %@, %d"), CFSTR("additional string"), 10);
    CFShow( outStr );
    CFRelease( outStr );
    return 0;
}

Можно это дело еще в макрос завернуть, наверное, но зачем? Вобщем, пусть так будет.

C++ не нужен?

В преддверии лекции Страуструпа, посвященной C++0x, наша маленькая но разношерстная команда разработки под Mac OS X разошлась во мнениях о ее потенциальной полезности. Одна часть, что показательно молодежь, утверждает что и от книги мозги вскипают, а от лекции и подавно поплохеет. Другая часть, постарше, ожидает что будет очень интересно.
Так вот, получается довольно интересная ситуация. Приток новых людей в область сильно сокращется, новички хотят иметь в языке как минимум сборку мусора, а старые разработчики уходят кто куда.
С учетом всего этого, очень интересно, что ждет область лет через 10. С одной стороны, плюсы это далеко не настольно незаменимы как чистый Си, с другой стороны, на данный момент это единственный язык позволяющий писать достаточно шустрый код для конечного пользователя. Но, как ни крути, такая ситуация будет не всегда, мощность компьютеров рано или поздно позволит работать программам на управляемых языках с приемлимой скоростью.
В целом, тенденция с сокращением количества C++ разработчиков не так уж и плоха, будем как кобол-динозавры, жутко редкими и жутко дорогими. С другой, скучно, наверное, в говнищах 30 летней давности копаться? Но, на данный момент, описанная выше тенденция сплошной плюс – зарплаты растут, количество вакансий растет, интресных задач все больше.

Перехват вызовов DTrace

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

Изучение DTrace стоит начать с его “эпицентра” (как утверждают авторы фрэймворка) – функции dtrace_probe. В целом, код достаточно понятен и содержит в себе большое количество комментариев. После внимательного изучения этой функции становится ясно, что искомое находится чуть дальше, в функции fbt_provide_module, которую можно найти в файле fbt.c директории bsd/dev/dtrace исходных кодов XNU. Эта функция как раз и используется для патчинга перехватываемых функций.
Итак, что же он делает в случае с 64 битной архитектурой (для 32 битной или PPC используется тот-же принцип)? Сам процесс установки перехватчика выглядит следующим образом: у каждого модуля берется таблица символов (struct nlist_64) и каждый глобальный символ, имеющий имя, сверяется со списком требуемых “проб”. При этом DTrace отказывается перехватывать ряд системных функций, например, такие как собственные функции (dtrace_*), функции, отвечающие за отладку (kdp_*, kdbg_*, kernel_debug), работу таймеров (etimer_*, clock_*, absolutetime_to_*), управление питанием (acpi_*), и ряд других. Если найденную функцию необходимо перехватить, то делается проверка того, что функция начинается со следующей последовательности команд (адрес начала функции получается из той же таблицы символов):

push %rbp                       ; 0x55
mov %rsp, %rbp                  ; 0x48, 0x89, 0xE5

Затем ищется конец функции. Если все прошло успешно, то создается “проба”, в которой сохраняется внутренняя информация, включая информацию о найденных адресах. На этом создание пробы завершается и начинается обработка следующей функции из списка.

Ну, а теперь самое интересное. После того как проба становится активной (функция fbt_enable), второй байт от начала перехватываемой функции подменяется на 0xF0. Копирование происходит по физическим адресам в памяти при помощи функции ml_nofault_copy. В результате возникает гарантировано неверная команда и, как следствие, возникновение исключения при попытке ее обработки процессором. Ну, а дальше все довольно очевидно. Возникающее исключение перехватывается DTrace, и идет обработка сработавшей “пробы” с последующим вызовом оригинальной функции.

Лично мне механизм очень понравился, но, к сожалению, при всей своей элегантности алгоритм, используемый DTrace, не подходит для использования в сторонних приложениях, т.е. воспользоваться им, конечно, можно, но ни к чему хорошему это не приведет.

Грабля в launchd

Неожиданно нарвался на граблю там, где уж совсем не ожидал ее найти, а именно в launchd. Вообще, launchd крайне удобен в управлении демонами, например при помощи него мы при запуске GUI из под пользователя поднимаем рутовый демон. Делается это довольно просто, создается plist и в переменную WatchPath указывается директория, по изменении которой должно запуститься приложение из ProgrammArgument. Сам plist помещается в директорию LaunchAgents для пользовательских приложений и LauchDaemons для системных.
Описанная выше часть работает как часы, и все бы ничего, если бы не возникла необходимость запустить по событию не только демон, но и само GUI. Казалось-бы все просто – добавляем WatchPath со ссылкой на ту же директорию, что и в случае с нашим демоном и вперед. Но вот тут то и зарыта грабля – демон стартует великолепно, а GUI нет. Чисто теоретически, такого происходить не должно, особенно с учетом того, что за директорией LaunchAgents следит launchd запущенный из-под пользователя, а за LauchDaemons рутовый. Но, это только теория, на практике все несколько иначе.
Лечиться созданием отдельной директории, используемой для запуска GUI приложения, которое уже в свою очередь поднимает демон.

Работа в Москве

Похоже, что московское АйТи таки оправилось от кризиса (ну, либо думает что оправилось).  В крайнем случае в области разработки на C++ зарплаты уверенно ползут вверх. С помощью МоегоКруга и hh.ru, получилось собрать довольно интересную информацию, такую как:

  • Зарплату в 150 тыр на старте давать никто не хочет (блин, обидно), хотя, по слухам, где-то такие ЗП есть!
  • На 130 тыр вакансии уже есть, но их чертовски мало. Тем не менее, очень хорошему разработчику на C++ или архитектору дать могут.
  • До оплаты в 100-110 тыр созрели почти все более-менее уважающие себя компании. Это очень приятный момент.
  • Ниже 100.. ну это уже не интересно, хотя, надо признать, таких вакансий не мало (если не сказать что дохрена), но и требования там довольно не серьезные.

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

Что не так с Java?

Меня долгое время мучил вопрос, почему приложения на Java такие тормозные? Если верить синтетическим тестам на The Computer Language Benchmarks Game, то в среднем приложения на Java работают всего-то в 2 раза медленнее при использовании памяти в 10 раз больше по отношению к C++ приложениям. В принципе, это не такой уж и большой разрыв. Хотя в этих тестах нет приложений на Objective-C, я думаю что результат для был-бы приблизительно тем же, ну может памяти тратилось бы не в 10, а в 5 раз больше, а ведь c приложениями на Objective-C можно нормально работать. Так в чем же дело?

Ответ пришел нежданно-негаданно. Заинтересовавшись Andoid, а как всем известно, приложения для него пишутся именно на Java, я похоже наткнулся на ответ. Как это ни печально, дело в программистах. Разработчики Java живут в мире неограниченных ресурсов – бесконечной памяти, бесконечного дискового пространства и бесконечной производительности процессора. И в результате мы имеем очень сильное усложнение ПО с потерей производительности в угоду читабельности и простоты разработки. И если такая стратегия работает для корпоративного ПО, где пользователь будет работать с тем что ему дали (как бы то г-но что ему дали не работало), а мощность серверов действительно высока, то десктопное ПО, и уж тем более мобильно ПО начинает сталкиваться с серьезными проблемами.

Изучая примеры Open Source приложений под Android просто диву даешься, насколько безграмотно они написаны с точки зрения ограничения используемых ресурсов. Куча бесполезных абстракций, нагромождение паттернов (привет не понимаю зачем была создана GoF) и совершенно бездумное использование памяти. Достаточно показательный момент – основные советы разработчикам под Android сводятся к тому, что надо плодить меньше сущностей, больше думать о сложности используемых алгоритмов и менее фанатично относиться к ООП. Создается ощущение что появление GC, упрощение разработки и отдаление от проблем среды исполнения вызвало сильнейшую деградацию разработчиков.

Одно радует, теперь понятно почему на десктопах количество приложений написанных на C# или Java ничтожно мало