MetaProg

Описание: Разработка и отладка приложений. Упор на 3D-графику.

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#7 dyvniy » Чт, 17 июля 2014, 09:19:03

За что он ненавидит С++
http://habrahabr.ru/post/111403/
Спойлер
11 января 2011 в 02:31
Еще 5++ причин ненавидеть С++ из песочницы
C++*
Признаюсь Вам — я ненавижу язык С++. Он просто выводит меня из себя.

Когда я встречаю эти три символа в заголовках статей на любимых ресурсах — еще терпимо, когда вижу в подопытном проекте файлы с расширением .cc и .cpp, .hh и .hpp (и еще .hxx и .cxx, у-ух, тысяча чертей!) — уже злюсь, а уж когда приходится читать, или, еще хуже, писать на нем — да я почти что истекаю ядом!

Что ж, попробую заронить и в Вас искру священной ненависти.


0. С — это Вам не С с плюсами!


Ненавижу, когда код на чистом С, который сами Патриархи Керниган и Ритчи признали бы созвучным их Светлому Языку, кощунственно обзывают «С++» и бездумно заключают в файлы с одним из вышеуказанных нечестивых расширений, и, что самое страшное, и компилируют тоже как С++. Зачем?

1. С++ — это Вам не С с плюсами!


Это более чем весомая причина для ненависти. Многие авторы пособий, те самые отступники, кто продвигал язык в 90-е, утверждали, мол, что С++ — это «улучшенный» С, что можно, якобы, почти безболезненно перейти от С к С++!

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

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

«Новые» возможности С++ разлагают неокрепшие умы юных программистов.
По тлетворному влиянию С++ может поспорить с Delphi/VB в 90-х и PHP в 2000-х.

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

Лично я вижу три пути обучения программированию:
1) академический — от основ Computer Science — к функциональному, логическому и метапрограммированию;
2) инженерный — от аппаратных основ и Ассемблера к С и высокоуровневым языкам, параллельно изучаются мат. основы;
3) ремесленный (т.н. «быдлокодинг») — no comment;

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

Но вот прожить весь путь с одним языком?

Главная проблема С++ в том, что этот язык напрасно замахивается на первую и третью область сразу, когда корни его растут из второй. Для первой области надо заново собрать международный комитет и полностью переделать язык с нуля (и здесь тоже могла бы висеть картинка с троллейбусом из батона), для третьей — небрежности не терпит, и кривая обучения будет крутовата. Причем, взобравшись на очередное плато, ученик не поймет, насколько он высоко залез, и бросится пробовать свои силы, думая, что это поможет ему взять очередную высоту, и т.д. до посинения и преждевременной смерти под кучей собственного кода.

2. Вы все еще знаете, что под капотом у Вашего компилятора С++? Тогда мы идем к Вам!


Язык С обязан был своей всемирной популярностью двум основным факторам:
1) идеологическая и синтаксическая целостность;
2) прямая связь с низкоуровневым программированием;

Если Вы видели или даже трогали формальное описание грамматики языка С, то Вы понимаете, о чем я. Если Вы видели ассемблерные листинги своих и чужих программ на С, хоть при трансляции, хоть в дизассемблере и отладчике, то Вы понимаете, о чем я. Если Вы, черт побери, прочитали документацию к Вашему компилятору и компоновщику и способны хоть иногда угадывать, какие оптимизации применяются к Вашему коду и как он лежит внутри исполняемых модулей — Вы на коне! А не, извините, под ним.

Шаблоны(!), позднее связывание, RTTI… Все это потребовало новый уровень абстракции, еще больше отдалило код «на бумаге» от кода «в железе». Да на счету одного только безобидного name mangling миллиарды сожженных нейронов и тонны сточенного о клавиатуру эпителия!

Отладка, реверсинг и ручная оптимизация «приплюснутого» кода требуют стойкости в истинной вере и прямых рук, но где, где их найти, когда юных «плюсовиков» развращают с малолетства?

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

А С++-то быдлокодинга совсем не терпит, но как манит, манит его адептов!

3. Парадигма повержена? Да здравствует парадигма!


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

Но сама идея того, что можно из императивного языка сделать нестрогое подобие объектно-ориентированного, а затем сторонними библиотеками придать черты функционального, а потом использовать «это» как мультипарадигменный язык, порочна и б-гомерзка. А как реализуются эти нестройные расширения? Заглушки, и снаружи, и внутри. STL и его собратья хоть и придают языку (и среде) необходимую «мощь», но никак не целостность, и неотъемлемые теперь уже свойства языка реализованы и оформлены совершенно различными способами. Тогда уж можно и на голом С с препроцессором накрутить что угодно, что успешно и делается.

Кощунственно даже сравнивать С++ с метаязыками — его сила в гибкости, а не в изменчивости.

4. Синтаксис С++ убог


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

Посмотрим правде в глаза — то, как синтаксис С++ выражает его идеологию, сегодня вызывает ужас. Чтобы сделать код С++ читаемым, уже недостаточно прямых рук автора, нужна подсветка синтаксиса! А чтобы прочитать код, использующий STL или boost — буйство угловых скобок (про стандарт C++0x лучше умолчим), удвоенных двоеточий и преисполненных смысла слов — придется пожертвовать частью мозга, которая, при использовании более разборчивых в друзьях языков, могла бы заняться и более полезным делом.

Если бы С++ с самого начала родился бы без буквы С в своем названии, возможно, претензий к синтаксису ни у кого бы и не было.

5. Хотите ООП — так возьмите его живым!


А не довольствуйтесь метафизическим сношением с подобным Франкенштейном.

No, srsly. Вы вчера-сегодня выбрали С++, именно чтобы снимать сливки с использования ООП в своем проекте? ООП — это круто, Вы хотите найти идеальный баланс между проектированием, кодированием и (о, б-же!) сопровождением, начитались статей про модные в 90-х концепции? Передумайте, пока не поздно. На дворе не 95-й год, когда это еще прокатывало.

Есть, есть ниша, где С++ идеален и замены ему пока нет — это функционально богатые, монолитные проекты, требующие наивысшей производительности (и в которых ну совершенно нет места быдлокодингу).

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

Но С++ уже не место в мэйнстриме!

Есть, к примеру, замечательный язык Java, который обладает и ясным синтаксисом, и всеми необходимыми возможностями, да еще и транслируется в байткод удобоваримого формата. Есть не менее замечательный язык C#, который не отвечает за грехи своего предка, да и намного толерантнее, чем С++, к небрежному коду. А для промышленного языка это более чем критично.


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

За примерами далеко ходить не надо — в стеке технологий из Редмонда — DirectX и ATL/MFC. Доколе? Зачем? OpenGL и голое WinAPI отлично обходятся без плюсовых «наворотов» (Я умолчу об именах серьезных проектов, которые используют С, включая ядра всех популярных ОС и драйвера для них, популярные СУБД, полные тулчейны для множества языков, графические оболочки и даже, Вы не поверите, прикладное ПО!).

Нет, действительно достойные ООП-библиотеки и фреймворки существуют, взять тот же Qt, но в целом приятного мало — «ООП ради ООП» торжествует среди библиотек и превращается в «С++ ради С++» при выборе технологии для проекта, а хотелось бы думать, что мы — не те школьники, которых насильно обучили Delphi, потому что на нем окошки рисовать проще, или потому, что окошки можно рисовать только на нем.

Все эти годы, когда С++ доминировал на рынке, получается так, что он не выбирался низами, а насаждался верхами, и не всегда оправданно.




Вместо заключения


Я встречал мнение, что сила С++ — в сочетании его гибкости и нестрогого его соответствия канонам ООП, по сравнению с той же Java. Мол, не хотите — пишите вроде бы как бы и на С, а где надо — «раззудись плечо» и т.д.

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

То, что создатели С++ уже 20 с лишним лет пытаются отвечать желанию трудящихся усидеть на трех стульях сразу и накрыв одним языком всю предметную область, может быть поводом для уважения, но никак не должно влиять на выбор идеального инструмента для решения конкретной задачи.




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

Я всего лишь поделился с Вами тем, что тревожит меня при использовании данного языка и продуктов, так или иначе с ним связанных.

Если в наших с Вами силах своим выбором направлять движение всей отрасли, то пусть нами руководит разум, а не привычки и эмоции.
Спойлер
0. Это как ненавидеть Паскаль за то, что на нем быдлокодят школьники.
1. И снова во всем виноваты пользователи языка. Не понял, на что там «замахивается» C++
2. Вы говорите, что код на C++ сложно отлаживать. Но опять переводите все на «адептов»…
3. Так за что вы ненавидите C++?.. Как-то не очень ясно.
4. Ну, знаете, говорить, что «убог», это как-то сильно. Может быть не идеален, но это плата за гибкость.
5. «C++ не место в мейнстриме». Что вы под этим подразумеваете? «Топовые игры, браузеры» уже не мейнстрим, получается?
Чтобы запустить десктопное приложение на Java, нужно тащить с собой N метров JRE. А C# вон и то, только под виндой официально (будем надеяться, что Microsoft обратит внимание на Mono).
Какую замену C++ помимо этих двух языков вы можете предложить?

Я не говорю, что вы неправы, сколько людей, столько и мнений. На мое мнение можно вообще не обращать внимания, у меня нет достаточного опыта спорить о таких вещах. Но говорить, что C++ уже чуть ли не умер… Как-то не очень прилично что ли.
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#8 dyvniy » Вт, 22 июля 2014, 06:10:18

Концепция Boost
http://habrahabr.ru/post/210838/
Спойлер
Концепции Boost tutorial
C++*, Программирование*
От использования шаблонов в С++ лично меня всегда отпугивало отсутствие стандартных механизмов задания ограничений параметров. Другими словами, когда разработчик пишет функцию

template <class T>
bool someFunc(T t)
{
if (t.someCheck()) {
t.someAction(0);
}
}

он делает различные допущения относительно функциональности объектов типа T, однако не имеет стандартной возможности донести их до пользователей. Так приведенный пример предполагает, как минимум, следующее

Объекты типа T передаются по значению, значит, должны иметь открытый копирующий конструктор
Существует открытый метод T::someCheck без параметров, который возвращает значение, приводимое к логическому типу
Существует отрытый метод T::someAction, который может принимать один приводимый к числовому типу параметр

Проблема

Теперь, допустим, программист решил распространять someFunc в виде библиотеки. Как ее пользователь может узнать о существующих ограничениях?
Чтение документации к библиотеке. Если она есть и внятно написана. Но даже в этом случае никто не будет вычитывать документацию всех используемых библиотек перед каждым изменением своего кода. Помнить все условия наизусть тоже не каждому по плечу.
Изучение исходного кода библиотеки. Тоже занятие на любителя. Причем чем библиотека больше и свой проект сложнее, тем любителей меньше

Остается еще один вариант — по сути, единственный автоматический — отталкиваться от ошибок компиляции. Т.е. сделал изменение, не собирается, ищешь почему… Однако те, кто пользовался шаблонами C++ знают, на что могут быть похожи сообщения об ошибках. На что угодно, только не на подсказку вида «Исправь вот здесь, и все заработает». Иногда сообщение достаточно понятно, а иногда оказываешься в дебрях чужой библиотеки… Компилятор сообщает об ошибке в том месте, где она произошла — ему все равно, что первоначальный контекст использования там уже не восстановить.

Рассмотрим пример (мы еще вернемся к нему позже)

Нужно отсортировать список (стандартный контейнер). Ничего не предвещает, пишем

std::list<int>theList;
std::sort(theList.begin(), theList.end());

Не компилируется. В VS2013 ошибка выглядит следующим образом
error C2784: 'unknown-type std::operator -(std::move_iterator<_RanIt> &,const std::move_iterator<_RanIt2> &)': could not deduce template argument for 'std::move_iterator<_RanIt> &' from 'std::_List_iterator<std::_List_val<std::_List_simple_types<int>>>' c:\program files (x86)\microsoft visual studio 12.0\vc\include\algorithm 3157 1 MyApp

Но это полбеды — при клике по ошибке мы оказываемся в глубинах стандартной библиотеки algorithm вот в этом месте

template<class _RanIt,
class _Pr> inline
void sort(_RanIt _First, _RanIt _Last, _Pr _Pred)
{ // order [_First, _Last), using _Pred
_DEBUG_RANGE(_First, _Last);
_DEBUG_POINTER(_Pred);
_Sort(_Unchecked(_First), _Unchecked(_Last), _Last - _First, _Pred);
}

Первая реакция: «Чего?! Почему вектор сортировался, а список вдруг нет — у обоих контейнеров есть итераторы, оба знают о порядке элементов..» И ладно еще стандартная библиотека — этот пример избит, и программисты обычно знают, что случилось. Но представьте, что вас вот так без спасательного круга бросили в недра другой, не такой известной библиотеки…

Решение

Оказывается, решение есть. Инициатива изменения языка в этом направлении существует, но пока в стандарт не попала.
А вот библиотека boost поддерживает понятие концепций (concepts), с помощью которых можно создавать пользовательские ограничения для параметров шаблонов.

Алгоритм использования концепций следующий. Разработчик вместе со своими библиотеками поставляет описание необходимых для их корректной работы концепций. Пользователь может в автоматическом режиме тестировать все свои сущности на соответствие предложенным правилам. При этом ошибки уже будут гораздо понятнее, вида: Класс не поддерживает концепцию «Должен быть конструктор по умолчанию».

Используя boost, разработчик не обязан каждый раз конструировать концепции с нуля — библиотека содержит заготовки основных ограничений.

Рассмотрим пример для функции someFunc, приведенной в начале статьи. Первое правило — наличие копирующего конструктора покрывается готовой концепцией boost::CopyConstructible, для остальных придется написать тесты вручную.

#include <boost/concept_check.hpp>

template <class T>
struct SomeFuncAppropriate {
public:
BOOST_CONCEPT_ASSERT((boost::CopyConstructible<T>));
BOOST_CONCEPT_USAGE(SomeFuncAppropriate)
{
bool b = t.someCheck();// метод someCheck, с возвращаемым значением, приводимым к bool
t.someAction(0);// метод someAction с параметром, приводимым к числу
}
private:
T t; // must be data members
};

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

Пользовательские проверки могут быть реализованы с помощью макроса BOOST_CONCEPT_USAGE. Важно помнить, что все экземпляры, участвующие в тестировании (у нас это T t), должны быть объявлены как члены класса, а не как локальные переменные.

Когда концепция объявлена, на соответствие ей проверять можно с использованием того же макроса BOOST_CONCEPT_ASSERT. Допустим, у нас есть класс

class SomeClass
{
public:
SomeClass();
void someCheck();
int someAction(int);

private:
SomeClass(const SomeClass& other);
};

Протестировать его можно так

BOOST_CONCEPT_ASSERT((SomeFuncAppropriate<SomeClass>));

Пробуем запустить — сразу получаем ошибку
error C2440: 'initializing': cannot convert from 'void' to 'bool'

Причем при клике по ней, нас бросает на нарушенную строчку в определении концепции SomeFuncAppropriate (в BOOST_CONCEPT_USAGE), где можно легко понять причину проблемы — метод someCheck возвращает void вместо bool. Исправляет, пробуем еще раз…
error C2248: 'SomeClass::SomeClass': cannot access private member declared in class 'SomeClass' boost\concept_check.hpp

По клике на ошибке оказываемся в исходном коде концепции

BOOST_concept(CopyConstructible,(TT))
{
BOOST_CONCEPT_USAGE(CopyConstructible) {
TT a(b); // require copy constructor
TT* ptr = &a; // require address of operator
const_constraints(a);
ignore_unused_variable_warning(ptr);
}
...

Причем курсор указывает на строчку

TT a(b); // require copy constructor

Ах да — копирующий конструктор спрятан. Исправляем — теперь тест проходится (компилируется файл с BOOST_CONCEPT_ASSERT). Значит, класс SomeClass полностью соответствует ожиданиям разработчика функции someFunc. Даже если в будущем будут добавлены изменения, которые нарушат совместимость, проверка концепции сразу сообщит, в чем именно проблема.

Вернемся к примеру с сортировкой std::list с помощью std::sort. Выразим в виде концепции требования к сортируемому контейнеру. Во-первых, std::sort может работать только с контейнерами, которые поддерживают произвольный доступ (random access). Соответствующая концепция имеется в boost (boost::RandomAccessContainer), однако ее недостаточно. Также существует требование к содержимому контейнера — его элементы должны поддерживать оператор сравнения «меньше». Тут снова выручает boost с готовой концепцией boost::LessThanComparable.
Комбинируем концепции в одну

template <class T>
struct Sortable
{
public:
typedef typename std::iterator_traits<typename T::iterator>::value_type content_type;

BOOST_CONCEPT_ASSERT((boost::RandomAccessContainer<T>));
BOOST_CONCEPT_ASSERT((boost::LessThanComparable<content_type>));
};

Запускаем проверку

BOOST_CONCEPT_ASSERT((Sortable<std::list<int> >));

Видим
error C2676: binary '[': 'const std::list<int,std::allocator<_Ty>>' does not define this operator or a conversion to a type acceptable to the predefined operator boost\concept_check.hpp

Щелчок по ошибке отправляет нас в исходный код концепции RandomAccessContainer, давая понять, что именно она и нарушена. Если заменить std::list на std::vector, проверка концепции увенчается успехом. Теперь попробуем проверить на сортируемость вектор экземпляров SomeClass.
BOOST_CONCEPT_ASSERT((Sortable<std::vector<SomeClass> >));

Контейнер-то теперь подходящий, но отсортировать его все равно нельзя, так как SomeClass не определяет оператора «меньше». Об этом мы узнаем сразу
error C2676: binary '<': 'SomeClass' does not define this operator or a conversion to a type acceptable to the predefined operator boost\boost\concept_check.hpp

Щелчок по ошибке — и мы оказываемся в исходнике LessThanComparable, понимая, что именно нарушили.

Таким образом, концепции делают обобщенное программирование в C++ чуть менее экстремальным. Что не может не радовать!

Про boost
http://dev.mindillusion.ru/boost/
Изображение
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#9 dyvniy » Ср, 13 августа 2014, 15:28:49

Точки следования
http://www.viva64.com/ru/t/0065/
Спойлер
Точка следования

12.10.2011
Точка следования (англ. Sequence point) — в программировании любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих еще отсутствуют.

Их часто упоминают, говоря о языках Си и Си++, поскольку в этих языках особенно просто записать выражение, значение которого может зависеть от неопределённого порядка проявления побочных эффектов. Добавление одной или нескольких точек следования задает порядок более жестко и является одним из методов достижения устойчивого (т.е. корректного) результата.

Точки следования необходимы в ситуации, когда одна и та же переменная изменяется в выражении более одного раза. Часто в качестве примера приводят выражение i=i++, в котором происходит присваивание переменной i и её же инкремент. Какое значение примет i? Стандарт языка должен либо указать одно из возможных поведений программы как единственно допустимое, либо указать диапазон допустимых поведений, либо указать, что поведение программы в данном случае совершенно не определено. В языках Си и Си++ вычисление выражения i=i++ приводит к неопределённому поведению, поскольку это выражение не содержит внутри себя ни одной точки следования.

В Cи и Си++ определены следующие точки следования:

Между вычислением левого и правого операндов в операторах && (логическом И), || (логическом ИЛИ) и операторах-запятых. Например, в выражении *p++ != 0 && *q++ != 0 все побочные эффекты левого операнда *p++ != 0 проявятся до начала каких либо действий в правом.
Между вычислением первого, второго или третьего операндов в операторе условия. В строке a = (*p++) ? (*p++) : 0 точка находится после первого операнда *p++. При вычислении второго выражения, переменная p уже увеличена на 1.
В конце всего выражения. Эта категория включает в себя инструкции-выражения (a=b;), выражения в инструкциях return, управляющие выражения в круглых скобках инструкций ветвления if или switch и циклов while или do-while и все три выражения в круглых скобках цикла for.
Перед входом в вызываемую функцию. Порядок, в котором вычисляются аргументы, не определен, но эта точка следования гарантирует, что все ее побочные эффекты проявятся на момент входа в функцию. В выражении f(i++) + g(j++) + h(k++) каждая из трёх переменных: i, j и k, принимает новое значение перед входом в f, g и h соответственно. Однако, порядок вызова функций f(), g(), h() не определён, следовательно, не определён и порядок инкремента i, j, k. Значения j и k в теле функции f оказываются неопределенными. Следует заметить, вызов функции нескольких аргументов f(a,b,c) не является случаем применения оператора-запятой и не определяет порядок вычисления значений аргументов.
При возврате из функции, на момент когда возвращаемое значение будет скопировано в вызывающий контекст. (Явно описана только в стандарте С++, в отличие от С.)
В объявлении с инициализацией на момент завершения вычисления инициализирующего значения, например, на момент завершения вычисления (1+i++) в int a = (1+i++);.
В Си++ перегруженные операторы выступают в роли функций, поэтому точкой следования является вызов перегруженного оператора.
Рассмотрим теперь несколько примеров, приводящих к неопределенному поведению:

int i, j;
...
X[i]=++i;
X[i++] = i;
j = i + X[++i];
i = 6 + i++ + 2000;
j = i++ + ++i;
i = ++i + ++i;
Во всех этих случаях невозможно предсказать результат вычислений. Конечно, эти примеры искусственны и опасность в них видна сразу. Рассмотрим фрагмент кода, найденный анализатором PVS-Studio в реальном приложении:

while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
Powers_of_Two_Reversed[m_nCurrentBitIndex++ & 31]))
{}
return (m_nCurrentBitIndex - BitInitial - 1);
Компилятор может вычислить вначале как левый, так и правый аргумент оператора '&'. Это значит, что переменная m_nCurrentBitIndex может быть уже увеличена на единицу при вычислении "m_pBitArray[m_nCurrentBitIndex >> 5]". А может быть ещё не увеличена.

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

while (!(m_pBitArray[m_nCurrentBitIndex >> 5] &
Powers_of_Two_Reversed[m_nCurrentBitIndex & 31]))
{ ++m_nCurrentBitIndex; }
return (m_nCurrentBitIndex - BitInitial);
Этот код более не содержит неоднозначностей. Заодно исчезла магическая константа "-1".

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

Вопрос:

Скачал ознакомительную версию PVS-Studio, прогнал свой проект и получил такое предупреждение: V567 Undefined behavior. The 'i_acc' variable is modified while being used twice between sequence points.

Код

i_acc = (++i_acc) % N_acc;
Как мне кажется, здесь нет undefined behavior, так как переменная i_acc не участвует в выражении дважды.

Ответ:

Неопределенное поведение здесь есть. Другое дело, что вероятность проявления ошибки весьма мала. Оператор '=' не является точкой следования. Это значит, что вначале компилятор может поместить значение переменной i_acc в регистр. Затем увеличить значение в регистре. После чего вычислить выражение и записать результат в переменную i_acc. После чего вновь записать в эту переменную регистр с увеличенным значением. В результате мы получим код вида:

REG = i_acc;
REG++;
i_acc = (REG) % N_acc;
i_acc = REG;
Компилятор имеет на это полное право. Конечно, на практике, скорее всего он сразу увеличит значение переменной, и тогда всё будет работать так, как ожидает программист. Но полагаться на это нельзя.

Дополнительные ресурсы

Википедия. Неопределённое поведение. http://www.viva64.com/go.php?url=663
Терминология. Неопределённое поведение. http://www.viva64.com/ru/t/0066/
Википедия. Точка следования. http://www.viva64.com/go.php?url=664
Елена Сагалаева. Точки следования (sequence points). http://www.viva64.com/go.php?url=665
Klaus Kreft & Angelika Langer. Sequence Points and Expression Evaluation in C++. http://www.viva64.com/go.php?url=666
Дискуссия на сайте bytes.com. Sequence points. http://www.viva64.com/go.php?url=667

PVS Studio, вопрос цены.
http://savepearlharbor.com/?p=196312
= http://habrahabr.ru/company/pvs-studio/blog/196312/
Спойлер
PVS-Studio. Отсчёт цен в обратную сторону
Опубликовано 04.10.2013
Сохранить
1

PVS-Studio cost

Если вы зайдете на страницу покупки PVS-Studio, то увидите, что мы убрали цену и предлагаем связаться с нами, чтобы обсудить её в индивидуальном порядке. Пока это эксперимент, результат которого мы не можем прогнозировать. Исчезновение цены не обозначает её рост. Наоборот, мы хотим предлагать PVS-Studio, в том числе и по более низкой цене, для небольших команд. Если вам интересно, как изменялись цены и, почему мы хотим опробовать такую модель продаж, то желаю приятного чтения. Думаю, те, кто занимается не только разработкой, но и продажами своих продуктов, найдёт в статье что-то интересное для себя.




Как все начиналось


Изначально, мы (Андрей Карпов Andrey2008 и Евгений Рыжков evgeniyryzhkov) хотели создать один или несколько небольших продуктов, в духе shareware. Душа, насыщенная мотивационными книгами, требовала действия. Мысли были разные, но долго ничего не придумывалось. Не хотелось делать очередной клон чего-то. Если бы в то время уже были распространены iPad и прочие планшеты, думаю, наш выбор бы очевиден. Неизвестно, как сложилась бы альтернативная история, где мы попытались бы делать игры или что-то ещё. И теперь уже не узнаем. Но на тот момент придумать что-то было непросто. Индустрии iXxx ещё не было. Сайтами заниматься не хотелось.

И мы придумали, как тогда казалось, отличную идею. Мы решили делать несложный анализатор кода, который помогал бы программистам портировать свои приложения для 64-битных версий Windows. Актуальность была вполне оправдана. До этого на прежней работе мы столкнулись с ситуацией крайней сложной ловли блох в программах, которые начали использовать более 4 гигабайт памяти. Инструментов просто не было. Вообще.

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

Но человек предполагает, а бог Microsoft располагает. Переход от Win32 к Win64 растянулся на огромный период времени и пока окончания поддержки Win32 не видно. Как результат, никакого всплеска потребности на такие программы не возникло. Интерес был и есть, но очень слабенький. Так что можно сказать первый продукт Viva64 для поиска 64-битных ошибок заметной прибыли не принес. Скорее, получилось в 0. А по началу, вообще приходилась подрабатывать аутсорсом.

Следующие два направления оказались ещё более неудачными. Это был анализ параллельных программ, построенных на основе технологии OpenMP и библиотека разбора кода VivaCore. В 2008-2009 очень много разговоров ходило о многоядерной архитектуре. Мы по натуре люди доверчивые. Мы поверили, что ещё немного, и без параллельности будет не прожить. А царицей параллельности будет OpenMP. Этого тоже не произошло.

На основе библиотеки VivaCore мы планировали делать специализированные инструменты на заказ, но не получили ни одного интересного предложения. И мы потихоньку свернули это направление. Нет, какие-то общения были. Но они были в духе: «Сделайте нам вот такой специализированный анализатор кода и отдайте нам его вместе с исходным кодом. А мы Вам заплатим 50 000 рублей. А быть может, даже целых 100 000 рублей». Было даже не смешно.

Первые успехи нас ждали только с появлением инструмента PVS-Studio. В начале в него входил анализ 64-битных программ (Viva64) и OpenMP программ (VivaMP). Для популяризации и от безысходности мы начали потихоньку делать анализ общего назначения. В начале эта часть анализатора задумывалась бесплатной. Она должна была существовать для привлечения программистов к платному функционалу (Viva64/VivaMP). Мы не верили, что в ближайшие годы сможем хоть как-то конкурировать в анализе общего назначения с Gimpel PC-Lint, Coverity и прочими акулами разного размера.

А потом, нам написал человек. Он спросил, надо ли покупать PVS-Studio, чтобы использовать анализ общего назначения. Мы ответили: «Нет. Он бесплатен. Но мы будем рады продать ему Viva64/VivaMP и оказывать поддержку». Человек: «О нет, спасибо. Раз он бесплатен, то более мне ничего не надо. Спасибо за хороший бесплатный продукт. Если бы надо было купить анализатор общего назначения, я бы купил. Мне он нравится. А потребности в Viva64/VivaMP у меня нет».

И тут в голове у нас щелкнуло. Вот оно. Это интересно людям. И первый релиз PVS-Studio 4.00 вышел уже с платным анализатором общего назначения. С тех пор именно диагностики общего назначения приводят к нам больше всего покупателей. При этом и продвигать их оказалось намного проще. Демонстрируемые примеры найденных ошибок просты и поняты.

Все это, конечно, к делу мало относится, но думаю было интересно. А теперь поговорим собственно о ценах.


История цен


Как я уже говорил, изначально мы думали о простом инструменте с массовым потреблением. Изначальная цена на Viva64 была около $400. А вот дальше начался длительный пересмотр цены:

$800;
€1600;
€3500;
€5250.

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

Происходило это приблизительно так. К нам приходят крупные компании и покупают лицензию на одного (ОДНОГО!) разработчика. Мотивировали они такую покупку разными причинами. Например, это могло выглядеть так:

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

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

Представьте, вы подключаете людям в дома газ. Берете недорого, так как не так уж много по одной трубе в дом утекает газа. Пироги пекут, индейку жарят. Ради бога. А потом, кто-то в здании вместо газовых плит для готовки, открывает тепловую электростанцию. А платить больше не планирует, аргументируя свою правоту, что ему также нужна только одна труба. Конечно, здесь уместно было бы говорить об объеме газа. Жаль, что счетчик на проверку кода — сложная философская задача. Про это я немного поговорю далее. А пока, я думаю, вы понимаете, почему после очередного такого подключения ТЭС, мы отказались от одиночных труб (лицензий). Получается выгоднее продавать мало, но дорого. Печально.

Последние цены, которые были у нас на сайте, это:

€5250 за командную версию (до 5 человек);
от €9000 – Site License на большие группы разработчиков.

Естественно, основная цель таких лицензий и цен — это продавать Site License в крупные компании. И мы продаем такие лицензии. Немного, но продаем. Однако, получившаяся ситуация нам самим не нравится. Получается, что мы потеряли все мелкие и средние команды. Для них такая покупка неоправданная. Конечно, сделать сейчас версию опять за $500 не вариант. Я не буду приводить здесь все рассуждения и подсчеты. Поверьте на слово — мы будем зарабатывать меньше, а поддерживать большее количество людей. Однако, и оставить все как есть, плохо. Вот отсюда и идут наши поиски.


Идеальный подход к определению цены


Назначить дифференцированные цены для проектов разного размера кажется простым и правильным решением. Я выше приводил аналогию с газом. Чем больше человек потребляет газа, тем больше платит. Чем больше кода анализируется, тем больше и серьезней проект. Чем больше проект, тем больше денег разумно брать с команды.

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

Что делать с аутсорсерами, которые ведут не один постоянный проект, а много маленьких и средних? Одни проекты заканчиваются, другие начинаются.
Что собственно считать? Количество LOC очень шаткий параметр. А что делать со сторонними библиотеками? Как понять, что стороннее, а что нет? А если есть два основных проекта? А если это несколько веток одного проекта? А что, если компания разрабатывает только часть одного большого проекта?
Как защититься от обмана? Как понять, что убрали нолик из посчитанного результата? Городить сложный механизм для надежной передачи данных? А как понять, что начали проверять другие проекты?
И так далее.

Уверен, что многие захотят поделиться своими идеями на темы подсчета размера проекта. Прошу, не надо этого делать. Мы долго думали над всем этим и не смогли выработать адекватное и удобное решение. Если я не буду отвечать на выдвинутые идеи, это может показаться грубым. Вступать в длительные дискуссии я не хочу. Мы и так тут наобсуждались. Так что просто поверьте. Да, такой подсчет сделать можно. Но сделать это просто, элегантно и красиво — нет.

При этом такой подсчет все равно будет опираться на доверие к пользователям. Так что проще ничего не городить, а, пообщавшись с людьми, понять размер их команды и поставленных перед ними задач. А кто захочет обмануть, всё равно обманет.

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


Что сейчас с ценами PVS-Studio


Читатель, прочитав все вышесказанные, может задать резонный вопрос: «Так какие же сейчас цены на анализатор кода PVS-Studio?». Отвечаю.

Цена для полновесных компаний с большим проектом и командой до 9 человек, цена будет составлять около €5250. Около — потому, что кто-то может захотеть реализацию определенных пожеланий. Обратите внимание, что мы говорим о размере команды, а не о количестве пользователей программы.
Более крупные команды разработчиков или те, кому нужна расширенная поддержка, должны приобрести Site License, которая начинается от 9000 евро.
Небольшие команды могут рассчитывать на скидку. Скидка зависит от многих параметров. Пишите нам — пообщаемся.
Мы поддерживаем бесплатные Open-Source проекты и выдаем на время бесплатные ключи. Выдавать ключи на больший срок нам в тягость, так как все равно со временем требуется поддержка. Ведь таких пользователей будет становиться всё больше и больше. Поэтому для них мы можем предложить совсем недорогие варианты. В общем, пишите — найдем компромисс.


Хочу подчеркнуть: наша цель — отталкиваться от размера проекта/команды, а не от количества инсталляций. Поэтому под размером команды имеется в виду именно размер команды, а не того количества людей, которые будут непосредственно запускать PVS-Studio.

Ждем всех, кто заинтересовался в почте. Мы готовы к обсуждению различных вариантов покупки.

Триальная модель остается прежней. Версия имеет полную функциональность, но ограничена количеством «кликов». Кстати, скоро выйдет PVS-Studio 5.10, где мы представим первый вариант Standalone версии. Она будет способна проверять сгенерированные каким-либо образом *.i файлы. Это должно существенно расширить область возможного применения. Также, Standalone версия содержит интерфейс для работы со списком сообщений и текстом программы. Все это пока только для Windows. Однако, уже большой шаг вперёд.

Демонстрационная версия: Скачать PVS-Studio.
Страница покупки: Купить PVS-Studio. Можете писать напрямую на support@viva64.com .
Лучше всякой рекламы: Примеры найденных ошибок.

С уважением, ваш милый Единорожка.

ссылка на оригинал статьи http://habrahabr.ru/company/pvs-studio/blog/196312/

С++ Cat от того же производителя.
http://www.viva64.com/ru/b/0228/
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#10 dyvniy » Ср, 27 августа 2014, 16:56:43

tmp bookfi.org/book/1453938
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#11 dyvniy » Вт, 2 сентября 2014, 13:27:34

Женский язык программирования
http://whaves.ucoz.com/news/zhenskij_jazyk_programmirovanija/2013-07-06-60
Спойлер
Существует расхожее мнение о том, что женщины плохо справляются с программированием. И главная из называемых причин — несовместимость «женской логики» с той, которая используется в традиционных языках программирования.

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

Немного поразмыслив, я подобрал ряд примеров и приступил к рассуждениям. В начале своего повествования у меня не было четкого представления, каким будет финал, но я совсем не ожидал, что главным выводом станет заголовок этого рассказа: «JavaScript — женский язык программирования».

Если этот вывод вам показался интригующим, давайте еще раз пройдемся по цепочке моих рассуждений и, возможно, мы придем к какому-то иному выводу…

Представим любую полезную работу как движение поезда из точки «А» в точку «Б».

Поезда движутся по рельсам — это правила и законы, по которым выполняется работа.

Машинист — ответственный за выполнение работы: он поддерживает связь с общей системой, следит за составом, решает нештатные ситуации.

Для полноты картины скажем, что вагоны — полезный груз.

Наша задача: провести аналогию между женской и мужской моделью поведения (мышления) и одновременно сопоставить их с синхронным и асинхронным программированием. (Под асинхронным я и подразумеваю JavaScript, и прошу не придираться к терминологии.)

Поехали…

Мужская модель. Машинист садится в локомотив, трогается и продолжает движение по рельсам, пока поезд не достигнет станции назначения.

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

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

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

Женская модель. Машинист села в поезд, тронулась и… тут же выпрыгнула из него. Ей незачем сидеть в поезде, пока тот не потребует особого внимания. А чтобы во время вернуться в него, она завела будильник на 18 часов, и попросила проводницу из 9-го вагона скинуть ей sms, когда за окном лесной пейзаж сменится на городской.

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

Вернувшись на первый и обслужив остановку по расписанию, она вновь чудесным образом оказалась на втором поезде. Убедилась в отсутствии проблем и перенеслась на третий…

Таким образом, в женской модели главным плюсом является более эффективное расходование ресурса машиниста — он все время в работе.

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

Примерно так работает женская модель поведения. Примерно так работает и JavaScript.

Теперь немного усложним. Ведь в жизни, как и в программировании, редко встречаются прямые пути. Чаще всего по ходу любого дела появляются новые и новые.

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

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

Примерно так работает мужское поведение. Примерно так выполняются программы в классических скриптах.

В женской модели машинист просто добавит еще один поезд в расписание уже двигающихся и продолжит свои магические перемещения между ними.

С количеством новых поездов увеличивается и время между появлением машиниста в каждом из составов. И если поездов стало слишком много, вся система начинает тормозить.

Такова женская модель поведения. Примерно так ведет себя и JavaScript.

Давайте уже подведем некоторые итоги.

Мужская модель поведения (синхронная программа) стремиться полностью выполнить поставленную задачу, прежде чем заняться следующей. Когда женская (асинхронная) логика стремится максимально загрузить ресурсы работой, чтобы все задачи пусть медленно и рывками, но двигались.

В жизни все эти модели довольно легко пронаблюдать или даже вспомнить.

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

Женщина на хозяйстве. Пока муж мусорит в комнате, она поставила на плиту молоко для каши и побежала заряжать стиральную машину. Только что заработала «стиралка», а мама уже меняет подгузник ребенку и одновременно разговаривает по телефону с подругой. И не страшно, что что молоко немного убежало — пока левая рука помешивает будущую кашу, правая аккуратно вытирает с плиты белую пену…

Так работают модели поведения мужчины и женщины, так ведут себя синхронные и асинхронные программы.

Не знаю как вам, а моей жене к этому моменту уже «стало все понятно» и она убежала с ребенком за собакой, зазывая меня следом взмахами свободной от поводка руки.

Оставшись наедине со своими мыслями, я решил сделать выводы. Если модель поведения JavaScript так сильно похожа на модель поведения женщин, можно предположить, что:

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

б) с увеличением популярности асинхронного программирования вообще и JavaScript в частности — количество женщин-программистов должно вырасти;

в) JavaScript меньше подходит для программирования ответственных и точных задач, но значительно лучше справится огромным количеством вспомогательных дел;

г) синхронное и асинхронное программирование, как мужчина и женщина, в паре рождают наиболее качественный результат, нежели по отдельности;

д) если все выходит так сообразно природе, значит можно назвать JavaScript — женским языком программирования.

С чем я искренне поздравляю всех женщин и приглашаю в увлекательный мир написания полезных скриптов на JavaScript!
Ру Курепин
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#12 dyvniy » Ср, 3 сентября 2014, 11:19:07

Qt (C++) или Java
http://citforum.ru/programming/application/java_qt.shtml
Спойлер

Сравнение Qt и Java

A Comparison of Qt and Java
Автор: Matthias Kalle Dalheimer
Перевод: Andi Peredri
В этой статье сравнивается эффективность использования C++/Qt и Java/AWT/Swing для разработки программного обеспечения с пользовательским графическим интерфейсом.

1. Что мы сравниваем?

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

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

Целью этой статьи является сравнение C++/Qt и Java/AWT/Swing. Чтобы это сделать наиболее точно, мы сначала сравним языки программирования, то есть C++ и Java, а потом две GUI-библиотеки: Qt для C++ и AWT/Swing для Java.

2. Сравнение C++ и Java

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

2.1. Продуктивность программирования

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

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

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

Однако проведенное исследование показывает, что на практике сборка "мусора" и другие возможности Java не оказывают большого влияния на продуктивность программирования. Одна из классических моделей оценки программного обеспечения CoCoMo, предложенная Barry Boehm, предопределяет стоимость и сроки разработки программного продукта на основе стоимостных коэффициентов, которые учитывают такие факторы, как суммарный опыт программирования разработчика, опыт программирования на заданном языке, желаемая надежность программы и т.д. Boehm пишет, что независимо от уровня используемого языка, начальные трудозатраты всегда высокие. Подобная методика подсчета использовалась в другом исследовании, проведенном C.E.Walston и C.P.Felix, IBM, Метод измерения и оценки программирования (A method of programming measurement and estimation) .

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

Существует более позднее исследование, которое явно включает Java и которое подтверждает эту гипотезу. В Эмпирическом сравнении C, C++, Java, Perl, Python, Rexx и Tcl (An empirical comparison of C, C++, Java, Perl, Python, Rexx, and Tcl) Lutz Prechelt из университета Karlsruhe описывает проведенный им эксперимент, в котором студентам информатики поручили выполнить определенный проект и выбрать для его реализации, руководствуясь личными предпочтениями, один из языков программирования: C, C++ или Java (остальные языки были рассмотрены в другой части исследования). Собранные данные показали почти одинаковые результаты для C++ и Java (C был на третьем месте по многим параметрам). Эти результаты подтверждаются нашим собственным опытом: если программисты вольны в самостоятельном выборе языка программирования (чаще руководствуясь при этом своим опытом), программисты с равным опытом работы (например, измеренным в годах) достигают одной и той же продуктивности. Второй интересный аспект, который бы мы хотели отметить (но который не имеет формального экспериментального подтверждения), заключается в том, что менее опытные разработчики достигают лучших результатов с Java, разработчики со средним опытом разработки достигают одинаковых результатов с обоими языками программирования, опытные разработчики достигают лучших результатов с C++. Эти наблюдения могут быть объяснены тем, что для C++ доступны более совершенные средства разработки; и этот факт тоже должен быть принят во внимание.

Интересный способ определения продуктивности программирования предлагает метод функциональных единиц (Function Point), разработанный Capers Jones. Функциональная единица - это метрика программного обеспечения, которая зависит лишь от его функциональности, а не от конкретной реализации. Эта метрика позволяет использовать в качестве критерия оценки продуктивности программирования число строк кода, необходимых для обеспечения одной функциональной единицы, в свою очередь, уровень языка определяется числом функциональных единиц, которые могут быть созданы за определенное время. Интересно, что обе величины: число строк кода на единицу функциональности и уровень языка одинаковы для обоих языков (уровень языка: C++ и Java - 6, C - 3.5, Tcl - 5; число строк кода на единицу функциональности: C++ и Java - 53, C - 91, Tcl - 64).

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

2.2. Производительность работы приложений

Мы увидели, что преимущества продуктивности программирования на Java оказались иллюзорными. Теперь мы исследуем производительность работы приложений.

И снова Prechelt предоставляет интересные сведения. Объем предлагаемой им информации огромен, но в конечном итоге он приходит к заключению, что "Java-программы выполняются по крайней мере в 1.22 раза медленнее C/C++ программ". Заметьте, что он сказал по крайней мере; средняя же скорость работы Java-программ гораздо меньше. Наш собственный опыт показывает, что Java-программы выполняются приблизительно в 2-3 раза медленнее своих C/C++ аналогов. На задачах, ориентированных на интенсивное использование процессора, Java-программы проигрывают еще сильнее.

В случае программ с пользовательским графическим интерфейсом увеличение времени отклика интерфейса является более критичным, чем низкая производительность программы. Проведенные исследования показывают, что пользователи более терпимы к задачам, выполняющимся в течение двух или трех минут, чем к программам, которые не реагируют мгновенно на их воздействия, например, на нажатия кнопок. Эти исследования показывают, что если время отклика программы больше, чем 0,7 секунды, пользователи считают ее медленной. Мы вернемся к этой проблеме, когда будем сравнивать пользовательский графический интерфейс в программах Java и C++.

Объяснение того, почему Java-программы медленнее C++ проограмм, заключается в следующем. C++ программы компилируются компилятором C++ в двоичный формат, который затем исполняется непосредственно процессором; таким образом, выполнение программы осуществляется аппаратными средствами. (Это несколько упрощенно, так как большинство современных процессоров выполняют микрокод, но это не принципиально при обсуждении данного вопроса.) С другой стороны, компилятор Java компилирует исходный код в "байт-код", который непосредственно исполняется не процессором, а с помощью другого программного обеспечения, виртуальной машины Java (Java Virtual Machine, JVM). В свою очередь, JVM исполняется процессором. Таким образом, выполнение байт-кода Java-программ осуществляется не быстрыми аппаратными средствами, а с помощью более медленной программной эмуляции.

Для повышения производительности работы Java-программ были разработаны "Just in Time" (JIT) компиляторы, но универсального решения этой проблемы не существует.

На первый взгляд, полуинтерпретируемая природа Java-программ обеспечивает выполнение принципа "скомпилированный однажды код выполняется везде". Однажды скомпилированная в байт-код Java-программа может выполняться на любой платформе, для которой доступна JVM. На практике же, это не всегда так из-за отличий в реализациях разных JVM и из-за необходимости иногда наряду с Java-программами использовать родной, не-Java код, обычно написанный на C или C++.

Но разве использование платформенно-независимого байт-кода является верным подходом в создании кросс-платформенных приложений? С хорошим кросс-платформенным инструментарием, наподобие Qt, и хорошими компиляторами для различных платформ программисты могут достичь почти той же цели компиляцией своего исходного кода один раз для каждой из платформ: "написанный однажды код компилируется везде". Можно возразить, что для этого разработчикам потребуется доступ ко всем поддерживаемым платформам, в то время, как с Java, теоретически, разработчикам необходим доступ только к одной из платформ, имеющей средства разработки для Java и JVM. На практике же ни один из ответственных производителей программного обеспечения не будет сертифицировать свои программные продукты для платформ без предварительного их тестирования, поэтому в любом случае производителям будет необходим доступ ко всем поддерживаемым платформам.

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

2.3. Эффективность использования памяти

Java и C++ используют различные подходы в управлении памятью. В C++ управление памятью полностью осуществляется программистом, т.е. по мере необходимости распределение и освобождение памяти должно выполняться программистом. Если программист забывает освободить ранее полученную память, возникает "утечка памяти". Если во время работы приложения произойдет лишь одна такая утечка, проблем не возникнет, так как после завершения работы приложения операционная система освободит всю ранее использованную им память. Но если утечки памяти будут происходить постоянно (например, если пользователь будет периодически выполнять определенные действия), использование памяти приложением будет расти вплоть до полного ее расхода с последующим возможным отказом системы.

Java обеспечивает автоматическое освобождение неиспользуемой памяти. Наряду с распределением памяти программистом JVM ведет учет всех используемых блоков памяти и указателей на них. Если блок памяти больше не используется, он может быть освобожден. Это обеспечивает процесс, который называется "сборкой мусора". Он периодически вызывается JVM, проверяет все используемые блоки памяти и освобождает те из них, на которые отсутствуют указатели.

Сборка мусора очень удобна, но за ее использование приходится расплачиваться большим потреблением памяти и низкой произодительностью... Программисты C++ могут (и должны) освобождать блоки памяти сразу после того, как они перестали быть нужны. С Java блоки не освобождаются до очередного вызова сборщика мусора, периодичность работы которого зависит от использумой реализации JVM. Prechtelt предоставляет цифровые данные, утверждая, что в среднем, (...) и с вероятностью 80% Java-программы используют на 32 MB (или 297%) памяти больше, чем C/C++ программы (...). Вдобавок к большому расходу памяти процесс сборки мусора требует дополнительной процессорной мощности, которая в результате становится недоступной приложению, и это приводит к замедлению его работы. Поэтому периодическая работа сборщика мусора может приводить к "замораживанию" Java-программы на несколько секунд. Лучшие реализации JVM минимизируют такие замораживания, но не устраняют их полностью.

При работе с внешними программами и устройствами, например, во время ввода/вывода или при взаимодействии с базой данных, желательно закрыть файл или соединение с базой данных сразу же после того, как они перестали быть нужны. Благодаря деструкторам C++ это происходит сразу после вызова delete. В Java закрытие произойдет лишь во время следующего цикла работы сборщика мусора. В лучшем случае это может привести к излишней блокировке ресурсов, в худшем - к нарушению целостности открытых ресурсов.

Тот факт, что Java-программы используют блоки памяти большие, чем необходимо, является особенно критичным для встраиваемых устройств, объемы памяти которых невелики. Неслучайно, что до сих пор (на время написания этой статьи) не существует полной реализации Java-платформы для встраиваемых устройств, а лишь ее частичные реализации.

Главная причина, по которой сборка мусора является более дорогостоящей, чем непосредственное управление памятью программистом, - это утрата информации. В C++ программе программист знает и местонахождение своих блоков памяти (сохраняя указатели на них), и когда они перестанут быть ему нужными. В Java-программе последняя информация недоступна для JVM (даже если она известна программисту), поэтому JVM должна перебирать все блоки на предмет отсутствующих указателей. Для того, чтобы вызвать сборку мусора вручную, Java-программист может удалить все указатели на больше ненужные ему блоки памяти. Но со стороны программиста это потребует больше усилий, чем непосредственное управление памятью в C++; и тем не менее, во время сборки мусора JVM все равно придется проверить все блоки памяти, чтобы освободить неиспользуемые.

С технической точки зрения, нет ничего такого, что бы мешало реализовать сборку мусора в C++ программах. Существуют обеспечивающие это коммерческие программы и библиотеки. Но из-за перечисленных выше недостатков немногие C++ программисты используют их. Инструментарий Qt использует более эффективный подход для упрощения задачи управления памятью: при удалении объекта все зависящие от него объекты также автоматически удаляются. Подход Qt не мешает программистам по желанию самостоятельно удалять объекты.

Так как управление памятью в C и C++ обременительно для программиста, созданное с помощью этих языков программное обеспечение обвиняется в нестабильной работе и подверженности ошибкам. Хотя некорректная работа с памятью в C и C++ может привести к более критичным ошибкам (обычно приводящим к аварийному завершению программы), хорошие знания, инструментарий и опыт могут значительно уменьшить связанный с этим риск. Изучению управления памятью должно уделяться достаточно внимания. Также существует большое число коммерческих и свободных инструментов, позволяющих программистам обеспечить отсутствие в программах ошибок при работе с памятью; например, Parasoft Insure++, Rational Purify и Electric Fence. Гибкая система управления памятью в C++ делает возможным создавать адаптированные для любого типа приложений профилировщики памяти.

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

2.4. Доступные библиотеки и инструментарий

Java-платформа предлагает внушительное число пакетов, насчитывающих сотни классов для любых задач, включая пользовательский графический интерфейс, безопасность, поддержку сети и прочие. Это несомненное преимущество Java-платформы. Любому Java-пакету соответствует, как минимум, одна C++ библиотека, хотя иногда бывает очень трудно собрать в одном C++ проекте множество библиотек и заставить их вместе правильно работать.

Однако это преимущество Java является также ее недостатком. Разобраться в огромном API программисту становится все сложнее. Можете быть наверняка уверены, что для любой задачи всегда найдется уже готовое решение или, по крайней мере, решение, облегчающее выполнение этой задачи. Но найти пригодный для этого пакет и класс становится все труднее. Также с увеличением числа пакетов стремительно растет размер Java-платформы. В результате стали возникать ее "урезанные" версии, утратившие преимущества готовых решений. Таким образом, размер Java-платформы делает практически невозможным создание Java-систем небольшими производителями независимо от создателя Java Sun Microsystems, что ослабляет конкуренцию.

Если преимущества Java заключаются в доступных библиотеках, то явные преимущества C++ - в имеющихся средствах разработки. За все время существования семейства языков C и C++ было создано огромное количество самых разнообразных средств разработки, включая инструменты для дизайна, отладки и профилирования. Имеющиеся средства разработки для Java часто уступают по возможностям своим C++ -аналогам. Это справедливо даже для инструментов от одного производителя; например, сравните Java и C/C++ -версии профилировщика Rational Quantify.

Самым важным инструментом для любого разработчика, использующего компилируемые языки, является компилятор. Основным достоинством компиляторов C++ является скорость работы. Для обеспечения кросс-платформенности своих компиляторов (и других средств разработки) производители Java-инструментов часто сами используют Java, со всеми вытекающими отсюда проблемами производительности и эффективности использования памяти. Иногда встречаются Java-компиляторы, написанные на C/C++ (например, IBM Jikes), но это редкость.

3. Сравнение AWT/Swing и Qt

До сих пор мы сравнивали лишь языки программирования Java и C++. Но, как мы упомянули в начале этой статьи, язык программирования является лишь одним из аспектов, принимаемых во внимание при разработке GUI. Теперь мы сравним пакеты для разработки GUI, которые поставляются вместе с Java, т.е. AWT и Swing, и кросс-платформенный инструментарий Qt от норвежского производителя Trolltech. В сравнении мы ограничились лишь одним инструментарием C++, потому что в отличие от MFC (Microsoft Foundation Classes) и других подобных библиотек, Qt поддерживает все 32-битные Windows-платформы (кроме NT 3.5x), большинство разновидностей Unix, включая Linux, Solaris, AIX и Mac OS X, и встраиваемые платформы. Это позволяет максимально близко сопоставить платформы Java/AWT/Swing и C++/Qt.

3.1. Описание AWT, Swing и Qt

Инструментарий AWT (Abstract Windowing Toolkit) начал поставляться с самой первой версией Java. Он использует родные для платформ компоненты GUI (т.е. Win32 API для Windows и библиотеку Motif для Unix), обеспечивая таким образом переносную обертку. Это значит, что внешний вид и поведение AWT-программ будет отличаться на различных платформах, потому что именно они занимаются отрисовкой и управлением компонентов GUI. Это противоречит кросс-платформенной философии Java и может быть объяснено тем, что первая версия AWT была разработана за четырнадцать дней.

По этой и другим причинам AWT был дополнен инструментарием Swing. Swing использует AWT (и, следовательно, низкоуровневые библиотеки) только лишь для базовых операций: создания прямоугольных окон, управления событиями и отрисовки графических примитивов. Всем остальным, включая отрисовку компонентов GUI, занимается Swing. Это решает проблему отличающегося внешнего вида и поведения приложений на различных платформах. Но из-за реализации Swing-инструментария средствами Java его производительность оставляет желать лучшего. В результате Swing-программы медлительны не только во время интенсивных вычислений, но и при отрисовке элементов пользовательского интерфейса. Как уже говорилось, ничто не вызывает у пользователей такого раздражения, как большое время отклика интерфейса программ. Странно наблюдать за медлительностью перерисовки Swing -кнопки на современном оборудовании. Хотя с ростом производительности оборудования эта ситуация будет постепенно улучшаться, сложным пользовательским интерфейсам, созданным с помощью Swing, всегда будет свойственна медлительность.

При разработке инструментария Qt был использован тот же самый подход: низкоуровневые библиотеки используются только лишь для базовых операций, а отрисовкой элементов GUI занимается непосредственно Qt. Благодаря этому инструментарий Qt приобретает все преимущества Swing (например, схожесть поведения и внешнего вида приложений на различных платформах), и не имеет проблем, связанных с низкой производительностью, так как разработан на C++ и откомпилирован в машинный код. Интерфейс, созданный с помощью Qt, отличается быстрой работой, и, благодаря использованию кеширования, может быть быстрее интерфейса, разработанного стандартными средствами. Теоретически, оптимизированная не-Qt программа должна быть быстрее аналогичной Qt-программы; но на практике для оптимизации не-Qt программы потребуется больше усилий и мастерства, чем для создания оптимизированной Qt-программы.

И Qt, и Swing поддерживают технику стилей, которая позволяет программам независимо от платформы использовать один из стилей интерфейса. Это становится возможным благодаря тому, что отрисовкой элементов GUI занимаются непосредственно Qt и Swing. Вместе с Qt поставляются стили, которые эмулируют внешний вид Win32, Motif, MacOS X Aqua (в Macintosh-версии), и даже стиль, эмулирующий внешний вид Swing-программ.

3.2. Парадигмы программирования в Qt и Swing

Несмотря на то, что оценка API в определенной степени является делом личных предпочтений программиста, среди API-интерфейсов можно выделить такие, которые сделают ваш код более простым, кратким, элегантным и читаемым, чем другие. Ниже мы приводим два примера кода: первый с использованием Java/Swing, а второй с использованием C++/Qt, в которых реализуется вставка нескольких элементов в иерархическое дерево. Swing-код:

...
DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Root" );
DefaultMutableTreeNode child1 = new DefaultMutableTreeNode( "Child 1" );
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode( "Child 2" );
DefaultTreeModel model = new DefaultTreeModel( root );
JTree tree = new JTree( model );
model.insertNodeInto( child1, root, 0 );
model.insertNodeInto( child2, root, 1 );
...
Этот же код с использованием Qt:

...
QListView* tree = new QListView;
QListViewItem* root = new QListViewItem( tree, "Root" );
QListViewItem* child1 = new QListViewItem( root, "Child 1" );
QListViewItem* child2 = new QListViewItem( root, "Child 2" );
...
Как видите, Swing использует архитектуру Model-View-Controller (MVC), в то время как Qt ее поддерживает, но не навязывает использовать. Поэтому Qt-код более интуитивен. К такому же результату приводит сравнение кода для создания заполненной таблицы или других сложных компонентов GUI.

Вторым интересным моментом является то, как различные инструментарии связывают воздействие пользователя (например, выбор элемента в выше созданном дереве) с определенной функциональностью (вызовом функции или метода). Синтаксически в Java/Swing и C++/Qt это выглядит по-разному, но основной принцип общий. Трудно сказать, какой код является более ясным и элегантным, Swing-код:

...
tree.addTreeSelectionListener( handler );
...
или Qt-код:

...
connect( tree, SIGNAL( itemSelected( QListViewItem* ) ),
handler, SLOT( handlerMethod( QListViewItem* ) ) );
...
С одной стороны, Swing-код выглядит проще, а с другой - Qt-код более гибок. Qt позволяет программисту использовать для управляющей функции любое имя, в то время, как Swing обязывает использовать в качестве имени valueChanged() (вот почему в приведенном выше Swing-примере оно не было указано явно). Также Qt позволяет связывать событие (сигнал в терминологии Qt) с любым числом управляющих функций (слотов).

Таким образом, и Java/AWT/Swing, и C++/Qt одинаково хорошо подходят для разработки сложного пользовательского интерфейса. Главным недостатком Swing-интерфейса является низкая производительность Java.

4. Заключение

Мы сравнили две платформы: Java/AWT/Swing и C++/Qt, оценив их пригодность для эффективной разработки высокопроизводительных приложений с пользовательским графическим интерфейсом. В то время, как Java-платформа обеспечивает разработчикам сравнимую продуктивность программирования, платформа C++/Qt обеспечивает приложениям лучшую производительность и эффективность использования памяти. Также C++ выигрывает за счет более лучших средств разработки.

Что касается сравнения GUI-библиотек, Swing и Qt, то видно, что более худшая производительность Java-программ делает платформу Java/Swing менее подходящей для разработки GUI-приложений, даже при сравнимом опыте программирования. В отличие от Swing, Qt не навязывает программисту парадигму программирования Model-View-Controller, поэтому в результате Qt-программы получаются более краткими.

Независимое научное исследование и полученный практический опыт эксплуатации показывают, что предпочитаемость использования Java во многих случаях чаще всего неоправданна, а комбинация C++/Qt является более лучшей. Главными причинами этого являются более низкие производительность и эффективность использования памяти в Java (особенно при использовании инструментария Swing) при такой же обеспечиваемой продуктивности программирования. Во многих выполненных нами проектах начинающие программисты осваивали быстрее Java, более опытные и профессиональные разработчики (занимающиеся проектированием приложений и реализацией критичных участков программ) достигали быстрее лучших результатов с помощью C++.

Java/Swing может подойти для разработки некоторых программ, особенно если они без GUI-интерфейса или с ограниченной GUI-функциональностью. В целом, C++/Qt является более лучшим решением, в особенности для разработки GUI-приложений.
Менее опытные разработчики достигают лучших результатов с Java, разработчики со средним опытом разработки достигают одинаковых результатов с обоими языками программирования, опытные разработчики достигают лучших результатов с C++. Эти наблюдения могут быть объяснены тем, что для C++ доступны более совершенные средства разработки; и этот факт тоже должен быть принят во внимание.
Мы сравнили две платформы: Java/AWT/Swing и C++/Qt, оценив их пригодность для эффективной разработки высокопроизводительных приложений с пользовательским графическим интерфейсом. В то время, как Java-платформа обеспечивает разработчикам сравнимую продуктивность программирования, платформа C++/Qt обеспечивает приложениям лучшую производительность и эффективность использования памяти. Также C++ выигрывает за счет более лучших средств разработки.
Изображение


Название раздела: Программирование (под Desktop и Android)
Описание: Разработка и отладка приложений. Упор на 3D-графику.

Быстрый ответ


Введите код в точности так, как вы его видите. Регистр символов не имеет значения.
Код подтверждения
:) ;) :hihi: :P :hah: :haha: :angel: :( :st: :_( :cool: 8-| :beee: :ham: :rrr: :grr: :* :secret: :stupid: :music: Ещё смайлики…
   

Вернуться в «Программирование (под Desktop и Android)»

Кто сейчас на форуме (по активности за 15 минут)

Сейчас этот раздел просматривают: 101 гость