Pull to refresh

Comments 253

Я хочу оставлять меньше багов, потому желаю строгую типизацию, качественные сообщения об ошибках и статический анализ кода. Хочу упростить поиск ошибок, а, следовательно, хорошие отладочные средства и динамический анализ.
А Си-то тут причём?)
А где сами игры то? Хоть на Си, хоть на Go…
Мдя… игры выглядят как архтефакт 80-90-ых. Как то сразу вспомнились 8 бит и денди.
Ныне очень модное явление в Steam. Но многие из них, даже с такого уровня графикой, умудряются как-то многовато ресурсов жрать.
Потому что написаны на чём? Правильно, на чём угодно, но не на C. И даже не на плюсах наверняка. «Мы же пишем как бы старую игру на новые машины, тут производительность огого!»
Очевидно, что на разных языках тоже можно писать по-разному, забывая про оптимизацию. Я вполне уверен, что на С тоже можно написать медленного монстра. Винить в плохой производительности только сам язык (компилятор, рантайм), оторванно от контекста и конкретных решений (в том числе и архитектурных), по меньшей мере сомнительно.
Медленного монстра можно написать на чём угодно. Быстрого — нет. Впрочем, это вопрос задачи. Нередко для части, требующей хорошую производительность, пишут отдельный модуль на более подходящем языке.
Язык здесь не при чем. Оптимизация ведется на другом уровне — модели, текстуры, хитрые подмены 3d на 2d и прочие трюки. Ну а самое главное шейдеры — скелетная анимация, свет, тени и прочее. Сейчас большинство вычислений в играх делает на GPU.
Вы просто ничего не понимаете в искусстве pixelart'a :)
Кстати, первые 8 игр на C, следующие 17 на Flash.
Полагаю, вверху новые. Автор же сам пишет, что раньше писал на Flash, а потом тот умер.
Ну что сказать, это личный кактус отдельного человека, и он готов его кушать.
Я пишу на C потому, что именно на нем весь UEFI и написан, но я с удовольствием перешёл бы на Rust прямо завтра, если бы такая возможность была, т.к. от некоторых особенностей C уже мутит.
Синтаксис указателей на функции понимают два с половиной специалиста, молчаливое приведение типов приводит к очень хитрым ошибкам, от которых не спасает даже статический анализатор, практически все функции для работы с форматной строкой — одна большая дыра в безопасности, забытый volatile моежт обрушить цивилизацию в самом неожиданном месте, а макросами можно отстрелить себе не то, что ногу, а вообще все и несколько раз. Добавим сюда ручное управление памятью, которое почти никто не умеет делать правильно, буфера постоянного размера на стеке, и великий и ужасный NULL — изобретение на минус миллиард долларов. Несмотря на все это, С — прекрасный кроссплатформенный ЯП низкого уровня, и на низком уровне ему альтернатив практически нет. Подходит ли он для игр — надо спросить у автора, если он когда-нибудь вылезет из отладки.
Синтаксис указателей на функции понимают два с половиной специалиста

да что тут такого:
void (*variable)(int, int);

или
вовзращаемое_значение (*имя_переменной)(аргументы);

для объявления типа всё то же самое:
typedef вовзращаемое_значение (*имя_типа)(аргументы);


или через using в C++11:
using имя_типа = вовзращаемое_значение (*)(аргументы);


С указателям на методы класса (C++) чуточку сложнее, но тоже не rocket science, просто перед "*" добавляется Clazz::
вовзращаемое_значение (Класс::*имя_переменной)(аргументы);
typedef вовзращаемое_значение (Класс::*имя_типа)(аргументы);
using имя_типа = вовзращаемое_значение (Класс::*)(аргументы);
Правда с вызовом чуточку по другому:
Clazz foo;
(foo.*some_variable)(parameters);
Clazz *bar = new Clazz;
(bar->*some_variable)(parameters);


В обоих случаях нельзя хранить указатели в void*, как бы того ни хотелось — это отличие от обычных указателей нужно запомнить, хотя компилятор должен выдать предупреждение.
Все хорошо, да. А теперь то же самое, но для функции, которая сама указатель на другую функцию принимает, как qsort из стандартной библиотеки. Необходимость в typedef сразу же указывает на проблему, а если его не использовать, получится дикая смесь из скобок, запятых и имен типов, которую невозможно ни толком прочитать, ни толком понять.
Плюс еще нужно о соглашении о вызовах не забыть, если предполагается, что программа будет использоваться на голом железе, причем ключевое слово для него не стандартизировано и потому выглядит как макрос EFIAPI, который раскрывается либо в "", либо в __cdecl, либо в __attribute__((cdecl)), либо еще в какой-нибудь __кошмар.
Честно сказать, я не знаю, как нужно было сделать лучше. Но тот факт, что указатели на функции в их нынешнем виде мало кто понимает — вижу на работе стабильно раз в месяц.
а если его не использовать, получится дикая смесь из скобок, запятых и имен типов, которую невозможно ни толком прочитать, ни толком понять.

почти Lisp :-)

Плюс еще нужно о соглашении о вызовах не забыть, если предполагается, что программа будет использоваться на голом железе
Мне кажется, что это к C, как таковому, имеет сильно посредственное отношение: трудно стандартизовать то, что может поменяться. Вдруг кто напишет OS, где будет принято аргументы в функцию передавать через один: через стек, через регистр, через стек, через регистр, или передавать адрес возврата первым аргументом. Или последним. Или в середине. Ну и тяжёлое наследие x86. На x86_64 как бы не так много вариантов оставили. Ну и другие платформы есть.

Но тот факт, что указатели на функции в их нынешнем виде мало кто понимает — вижу на работе стабильно раз в месяц.
Вот тут, похоже, я встретился с тем, чего я не знаю или не совсем понимаю. По сути, какое отношение имеет указатель на функцию с соглашениями на вызов? Ну и примеры недопонимания можно привести, без вреда NDA и прочему POPI.
Я ничего против него не имею.
Мне он не нравится тем, что я вынужден проходить цепочку из макросов и typedef'ов, чтобы понять, что это за зверь вообще и как он будет выглядеть из окна аппаратного отладчика. Т.е. одно дело, когда на тебя из этого окна смотрит целая переменная со значением 17, и совсем другое — когда это не целая переменная, а указатель на целую, или на функцию, что еще хуже.
Сам по себе typedef — это благо, а проблемы такого рода решаются грамотными IDE, но они от этого не перестают быть проблемами.
Я тут рассматриваю не только язык сам по себе, но и все, что вокруг, поэтому директивы компилятора, разные #pragma и прочие __attribute__((declspec)) я тоже отношу к C, просто не к чему больше. Не спорю, опять же, что стандартизировать такое сложно, однако #pragma STDC ... как-то пробрались в C99, значит не все еще потеряно в этом смысле.

По поводу отношения соглашения о вызовах к указателям на функцию, простой пример: все компоненты UEFI находятся в одном адресном пространстве и общаются путем прямого вызова функций через таблицы указателей на них (такие таблицы называются «протоколами»). При этом, для архитектуры amd64 существует два распространенных соглашения о вызовах: Microsoft x64 (которое и используется в 64-битном UEFI и которое использует по умолчанию компилятор MSVC) и System V amd64 ABI, которое используют
*nix-системы и которое по умолчанию использует GCC. При этом компоненты UEFI не собираются одним и тем же компилятором, и зачастую поставляются в бинарном виде, поэтому очень важно, чтобы соглашение о вызовах было одинаковое, а это достигается добавлением EFIAPI ко всем typedef'ам всех указателей на функции, являющиеся частью протоколов, к прототипам и реализациям этих функций.
Пример кода
#include "Uefi.h"

// Define SIO DXE protocol GUID
#define EFI_CRSIO_DXE_PROTOCOL_GUID \
{ 0xc38bdbd, 0x6f6b, 0x49ae, 0x99, 0x5a, 0x1d, 0x6c, 0xc2, 0x9d, 0x95, 0xa1 }

// Extern the GUID for protocol users.
extern EFI_GUID gEfiCrSioDxeProtocolGuid;

// Forward reference for ANSI C compatibility
typedef struct _EFI_CRSIO_DXE_PROTOCOL EFI_CRSIO_DXE_PROTOCOL;

// Define each protocol interface function and the respective function pointer type 
typedef
UINT16
(EFIAPI *CRSIO_DXE_GET_INDEX) ( //Если вот здесь забыть EFIAPI, ничего не сломается, т.к. функция не имеет параметров. 
    VOID                        //А вот если бы они были, а компилятором оказался GCC, то он бы их положил в RDI, RSI...                         
);                              //а функция их ждет в RCX, RDX..., т.е. на вид все хорошо, а по факту в регистрах мусор вместо параметров

UINT16
EFIAPI // А если забыть его вот здесь, не забыв выше, будет почти то же самое, только упадет все в другом месте
CrSioGetIndex(
    VOID
);

// Define protocol data structure
typedef struct _EFI_CRSIO_DXE_PROTOCOL 
{
    CRSIO_DXE_GET_INDEX CrSioGetIndex;
} EFI_CRSIO_DXE_PROTOCOL;

Проблема то решена, но какой-то осадок неприятный все равно остается, т.к. четверть кода, который я пишу — макросы, обернутые в макросы, обернутые в typedef'ы, но альтернативы пока нет, спасибо, что не на ассемблере.
А! Теперь понял, вы про информацию о соглашении на вызов в самой сигнатуре при объявлении переменной или типа.
почти Lisp :-)

В Lisp хотя бы запятых нет :)
А теперь то же самое, но для функции,


Вроде такого (со SO):
Consider the signal() function from the C standard:

extern void (*signal(int, void(*)(int)))(int);


Perfectly obscurely obvious — it's a function that takes two arguments, an integer and a pointer to a function that takes an integer as an argument and returns nothing, and it (signal()) returns a pointer to a function that takes an integer as an argument and returns nothing.


>> Синтаксис указателей на функции понимают два с половиной специалиста
> да что тут такого:
> void (*variable)(int, int);

И сразу вопрос из зала: где тут имя функции?
Именно функции или переменной в которой можно сохранить указатель на функцию? ;-)
Вот об том и речь, что понимают два с половиной специалиста
ручное управление памятью, которое почти никто не умеет делать правильно

Два простых правила, чтобы забыть о проблемах с ручным управлением памятью:
1) удаляем память там же где и создаем
2) следующая вещь, которая пишется после new — это delete

В тоже время особенности работы GC для каждого отдельно языка содержат многостраничные мануалы о том, как правильно управлять памятью, как делать нужно и как делать не нужно… Не говоря уж о тонне костылей, типа андроидовского Out of Memory, если у Bitmap не вызвать recycle.
А что делать с обработкой ошибок, копировать все вызовы free() в каждый if (EFI_ERROR(Status)) или использовать goto fail, или может быть do { if (error_condition) break; } while(0), если у вас на goto аллергия?
Если бы простыми правилами можно было бы обойтись, память не текла бы у практически всех программ на С сложнее hello.c. Язык не виноват, но люди устроены именно так.
А по мне, так это, пожалуй, единственный вариант где goto применим. Отчего аллергия-то? Ну или сделать свой аналог try/throw/catch/finally при помощи setjump/longjump если вообще сыпью покрываетесь :-)
По мне тоже, но случается и вот такое, причем у всех, даже у самых гурушных гуру. У нас там все проще в UEFI — прошивка отрабатывает быстро и умирает быстро, а всю память, выделенную как EfiBootServicesCode или EfiBootServicesData освободит менеджер BDS, просто пометив ее свободной в UEFI MemMap, и ее потом перепишет ОС, когда ей понадобится. Т.к. мало кто в прошивке пытается выделить себе с кучи гигабайт-другой, то вся система довольно спокойно отностится к утечкам — в конце все равно останутся только пара рантайм-сервисов, для которых управление памятью уже написано и работает нормально.
> Два простых правила, чтобы забыть о проблемах с ручным управлением памятью:
> 1) удаляем память там же где и создаем
> 2) следующая вещь, которая пишется после new — это delete

А что делать с событийной моделью, когда например работаешь с буфером обмена? В обработчике drag создается объект, указатель на который пихается в буфер, При drop объект должен быть удален. Получается что new и delete расположены вообще в разных местах. Ваши правила не работают.
Для сложных случаев использовать подход, типа gobject с счётчиками и _ref/_unref? Ну как вариант.
Ваши правила не работают.

Очевидно, что правила не работают, если их неправильно применять.
Зачем вы в Drag суете указатель? Почему не сам объект, например? Он слишком большой? Тогда надо передавать не указатель, а его владельца с индексом, например.
Да, он слишком большой.

Какой индекс имеется в виду?
В C нет new и delete, там malloc и free — те еще кактусы, особенно в комплекте с привычкой C неявно приводить типы.
У меня другие правила: (1) использовать valgrind, (2) функциональные тесты под valgrind.
Можно подробнее о функциональных тестах под valgrind?
В современном С++ new и delete не используются. Smart pointers (unique_ptr), RAII, move семантика решают проблемы с памятью. Проблема не с памятью, а с ownership, shared state и object lifetime. Многие проблемы «старого» C++ (до C++11) решены, но по-прежнему использование С++ требует высокой квалификации программиста. Фишка С++ в том что на нем можно писать как высокоуровневые вещи (в отличие от С) так и очень низкоуровневые (в отличие от Java/C#). Поэтому в game dev С++ основной язык. Rust, Go, D как системные языки — все еще несколько сыроваты на настоящее время.
Два простых правила, чтобы забыть о проблемах с ручным управлением памятью:
1) удаляем память там же где и создаем
2) следующая вещь, которая пишется после new — это delete


А если между new и delete возникает исключение?

Два простых правила:
1) Не используем new и delete (если мы не писатели низкоуровневых либ)
2) Используем умные указатели
Вы так говорите, как будто new и delete должны быть в одном методе.
Если они в одном методе — они вообще не нужныю
«Там же» — в том же логическом пространстве.
По поводу smart_ptr… Холивар будет. :) Так что пожалуй не буду ничего писать.
С чего вы взяли, что я говорю, что они в одном методе? Я говорю между вызовами new и delete может возникнуть исключение.

Напишите, что вас не устраивает в умных указателях, интересно же. Не холиварно, а конкретно.
А почему конкретно «исключение»?
Может произойти тысяча вещей, которые поломают логику и эти вещи надо учитывать.
Если исключение поломало нам все, то уже пофиг на утечку будет. Если же мы исключение нормально обработали или штатно подавили — оно не повлияет на дальнейший delete.

По поводу не холиварно, а конкретно — все холивары начинаются с конкретных высказываний. :)
Ну ладно, один конкретный пример: нужно учитывать циклические ссылки и держать весь путь мигрированя указателя, чтобы не допустить цикличности. По сути это слабо отличается от необходимости держать такой же путь для обычного указателя, чтобы предсказуемо его удалить.
Исключение можно обрабатывать и позже, но leak ресурса уже возникнет. А так — RAII сработает при unwind'е.

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


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


И, кстати, вы таким образом переизобретёте shared_ptr.
В Rust хорошая интероперабельность с C (но не с C++). Может пора попробовать его использовать?
Уже однажды попробовал и не смог.
Среда UEFI достаточно сильно отличается от привычной стандартной библиотеки, к примеру, там почти все строки — в UCS2, и уже одно это приносит кучу боли, т.к. строковые литералы просто как L«String» теперь не запишешь — в Rust UTF8 везде. Плюс абсолютно всё получается внутри unsafe {}, и в итоге преимуществ у Rust почти не остается, а недостатки вроде необходимости писать врапперы к библиотекам и протоколам — никуда не деваются. Я видел пару попыток, но их авторы тоже бросили, практически не начав.
Если время найду, попробую еще раз.
Кстати, а C++ без RTTI и stl имеет смысл использовать, сделав внешний интерфейс на чистом C, или слишком много внешних вызовов? По сути, использовать от C++ возможность работать с объектами, а не структурами (RAII для управления памятью/ресурсами), и шаблоны.
Не получится, т.к. нет рантайма (т.е new, delete и прочие красивости C++ просто не работают), а без него C++ мало чем лучше C. Вот тут Count Chu рассказывает, как он заводил код на C++ в EDK2, но успехом я такое решение точно не назову.
Можно же сделать operator new (и остальные 3 штуки), которые будут звать malloc/free из «рантайма», который у вас уже есть. Или тоже не катит?
Катит, но там не только их не хватает, а в итоге нет ни исключений, ни стандартной библиотеки, и непонятно, зачем вообще огород городить, если на C уже все готово. В Rust хотя бы borrow checker поможет и иммутабельность по умолчанию, а чем хорош C++ в данном случае я просто не знаю.
UFO just landed and posted this here
UFO just landed and posted this here
Всем, libstdcpp и STLPort — два разных рантайма с разными же возможностями (и размерами), первый предосталяет только некоторые функции, а второй — всю стандартную библиотеку.
UFO just landed and posted this here
В игровых движках на С++ как правило используют свои контейнеры и строки. STL используется, но в ограниченном объеме. Есть специально заточенные STL реализации типа EASTL, tinySTL.
UFO just landed and posted this here
Блин, вот про исключения то я забыл, без них совсем печаль, да.
UFO just landed and posted this here
Автор зазнайка? Или это такой стиль? Везде «Я», в оригинале «I».
Как-то тяжело читать.
Английский язык, по моим наблюдениям, часто страдает излишними повторами слов, которые в русском либо просто не очень хорошо выглядят, либо наводят на подобные мысли. А вообще, автор же лично о себе пишет.
Английский язык совсем не страдает от этого :) А вот переводы на русский — почти всегда.
Англичане жуткие собственники и частое повторение «I» в речи – абсолютно нормальное явление.
А, мне кажется, это связано с меньшим количеством падежей и гораздо меньшим их использованием в английском языке. В русском языке ситуация иная: во-первых, «я» склоняется по падежам (+ меня, мне, мной; в английском только + me), а во-вторых, местоимения часто можно опускать, т.к. их с успехом заменяют суффиксы глаголов (работаю, работаешь и т.п.).
Бедняжки, надо им отсыпать немного.
В научных работах часто предложение начинается с «We»
В русском языке мы тоже используем местоимение «мы» в отношении себя.
Ну, SDL-то написан на C, так что ничего особо удивительного в написании игр на нем нету.

Меня не интересует красивая реалистичная картинка, зато я забочусь о производительности.
А нахрена производительность в играх, кроме как на красивую картинку?:)
Якобы чтоб ограничения производительности не мешали ему внедрять новые фичи.
Таких игр — примерно одна на все остальные, и ОП не входит в число ее создателей, так что не считово:)
Таких игр — примерно одна на все остальные

Minecraft, Terraria, Starbound, Gnomoria, Towns, Timber and stone, KeeperRL (open source кстати), CDDA.
Это первое, что в голову пришло. А их ещё много.
SDL написан на С, чтобы его можно было подключить к любому языку.
Я думаю это не аргумент, с таким же успехом можно было написать SDL на с++.
Я согласен в чем-то с автором, т.к. C очень хорош именно для игр.
Весь opengl api это С, shaders тоже по сути C-like syntax. Так зачем использовать зоопарк языков, когда все можно писать на одном.
Написать на С++ и потом городить экспорт?
Тем более SDL — набор отдельных модулей с низкой связанностью, без ООП там вполне можно жить.

Я согласен в чем-то с автором

Ну а я не согласен. Просто не могу представить архитектуру сколь-либо сложной игры на plain С. Может у меня заболевание мозга уже и я не мыслю программирования без ООП. Но весь опыт проектирования говорит мне, что С++ код при прочих равных будет чище аналогичного кода на plain C в сложной игре.

Весь opengl api это С, shaders тоже по сути C-like syntax

Поэтому все первым делом и пишут ООП обертку над OpenGL и шейдерами. :))
Например, я не могу представить как будет выглядеть мультирендер на plain C.
На С++ это просто абстрактный интерфейс и отдельная реализация для каждого рендера.
А на plain C?
Указатель на функцию? Я не защищаю C, но всё, что нужно помнить про ООП в C++ — это обёртка, скрывающая указатели от конечного программиста.
То есть лучшее решение — эмулировать ООП, вручную устанавливая указатели.
«Эмулировать ООП» — это слишком сильный и смелый термин. В данном контексте даже неверный, ведь нужно просто хранить адрес следующей инструкции.
Скорее обратно, ООП приходится реализовывать через такие грязные методы. Опять же, C++ — это не полноценный ООП, так как нету полноценных сигналов-слотов. Qt куда ближе, но он реализован через ещё более грязные методы: и строковые описания, и генерация методов, и куча чего ещё.
А почему вы считаете что слот-сигнальная система является неотъемлемой частью настоящего ООП?
Ну, возможно, потому, что она таковой и является? Да, я использовал неверный термин, объекты обмениваются сообщениями, но где в C++ сообщения, без Qt или бустовой магии, которую ещё собрать нужно?
class cA{
public:
  foo();
};

class cB{
  cA* m_a;
  bar(){
    m_a->foo();
  }
}

Здесь есть обмен сообщениями, но нет слот-сигналов. Откуда вообще мысль, что обмен сообщениями привязан к слот-сигнальной системе и не может быть реализован другими методами?
Броадкаст сообщение покажите?
Откуда мысль что бродкаст является обязательной частью ООП?
Не является. Но, например, передача сообщений между потоками — является. Удачи с доказательством.
Брр. Вы сейчас говорите, что два потока на С++ не могут обмениваться сообщениями? :)
Не в том виде, что вы указали.
Так в том и фишка. Нет никаких требований к способу обмена. Обмен есть, остальное не имеет значения.
Если так подходить к вопросу, вы правы. Но тогда не вижу причин не причислять да даже тот же чистый C к ООП языкам. При желании можно даже наследование сделать и много чего другого интересного сделать, года два назад были подобные статьи.

Я очень уважаю ваше желание отстоять звание C++, но взгляните правде в глаза. Это не то ООП, которое создали вместе со SmallTalk.
Я не против других подходов к определению ООП. Но мне пока не понятно откуда вы берете свои доводы.
Определенно C++ не SmallTalk. Но ему им быть и не надо.
И на plain C можно эмулировать ООП. Также как на С++ можно эмулировать слот-сигнальную систему.

Вы подняли сейчас верную тему. ООП — методология проектирования, я не свойство языка. Просто есть языки, на которых эта парадиграм реализуется просто, а есть те, на которых сложно. И всё.
Строго говоря, здесь нет обмена сообщениями. Здесь вызов функции, как в старом добром процедурном языке, просто выглядит иначе. Обмен сообщениями подразумевает, что та сторона, которой отправлено сообщение, сама решает, что делать с сообщением — например, может от него отказаться.
Получив сообщение через вызов метода объект может делать с ним все что захочет. Например, не обрабатывать.
Пожалуйста, укажите мне источник вашего мнения относительно сообщения применительно к ООП.
Я вот тут уж начал сомневаться, полез на википедию и там тоже не нашел ничего, что ставило бы к сообщениям в ООП обозначенные вами и iCpu требования.
Ну, как, концепция «Сообщения» обязывает.
Возьмем ObjC, канонический ООП язык. Отправителя автоматически не получить, броадкаст сообщений нет. Даже посылка сообщений максимально зарезана в пользу статического анализа, что выглядит уже как методы обычные.
А что до С++, передавайте отправителя аргументом.
en.wikipedia.org/wiki/Object-oriented_programming#Dynamic_dispatch.2Fmessage_passing

By definition, it is the responsibility of the object, not the external code, to 'on-demand' select the procedural code to run/execute in response to a method call, typically by looking up the method at run time in a table associated with the object.


В данном случае вызов метода ничем не отличается от вызова обычной функции, потому что метод, по сути, функция и есть. Понимаете, то, что управление передано в метод, уже означает, условно говоря, что объект «получил» «сообщение». Посмотрите, как ООП реализовано в Smalltalk, например — это именно то ООП, которое изначально подразумевалось под этим термином.

Однако, на самом деле, это действительно просто вопрос терминологии. То, что под терминами подразумевается, постоянно меняется, и то, что C++ и Java на протяжении всего их времени жизни называли ООП-языками, сместило/расширило суть понятия ООП. Поэтому называть ООП-языками C++, Java и прочие, на мой взгляд, вполне приемлемо. Например, та же штука произошла, хоть и более быстро, с термином RESTful. Что только под ним сейчас ни понимают, но в 99% случаев это даже близко не напоминает то, что изначально подразумевалось в исходной работе, где этот термин был введён.

Собственно, я только против того, чтобы вызов метода называли отправкой сообщения, потому что по сути это не так.
Эм… впервые слышу что сигналы слоты являются обязательной атрибутикой ООП. Всегда считал что язык поддерживает ООП если в нём есть полиморфизм, наследование и инкапсуляция.
Эти критерии не тождественны объектно-ориентированности. Например, в Go есть всё, что вы указали (окей, наследование с натяжкой), но он не является объектно-ориентированным. Когда в Rust добавят простое наследование структур (такие RFC уже есть), он тоже будет содержать все эти концепции, но он также не будет ОО.
Да что там go, по таким критериям Haskell — один из самый Ъ ООП языков, там даже полиформизм круче!
Опять эта тема. Является он объектно-ориентированным, потому что покрывает все его принципы. Это тоже самое, что smalltalk и С++. Последний не обязан быть SmallTalk, чтобы быть ООП языком. Тоже самое с Go — это другой язык, но инкапсуляцию, наследование и полиморфизм своими фичами он вполне покрывает. Именно в этом определение ООП языков и не надо тут выдумывать науку из этого целую.
Нет, определение ООП-языков не в этом.
Object-oriented programming (OOP) is a programming paradigm based on the concept of «objects», which are data structures that contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods.

Объектно-ориентированный язык так или иначе поощряет проектирование и программирование в терминах объектов и взаимодействия между ними. Если это не так, то язык не является объектно-ориентированным. Наличие полиморфизма (кстати, какого именно? Параметрического, ad-hoc, подтипов?), наследования и инкапсуляции не является ни необходимым, ни достаточным условием. Например, в Python инкапсуляция отсутствует в принципе — вы всегда можете добраться до чего угодно в любом объекте, но он от этого не становится менее объектно-ориентированным. И наоборот, ни Go, ни Rust не поощряют проектирование в ООП-терминах (объекты, классы, иерархии и прочее), поэтому они не являются ООП-языками.
Еще как поощрается, только в своих терминах, которые тем не менее покрывают принципы ООП. Опять начинаются эти придирки к словам вместо того, чтобы смотреть на суть вещей. В Go есть объекты, есть интерфейсы, есть иерархии (да, немного не такие, к каким мы привыкли, те менее есть). Поощрается взаимодействие между ними. Более того, на нем никак иначе и писать невозможно.
Что до раста, то беглый просмотр в гугле приводит к выводу, что Rust мультипарадигменный язык, в том числе и объектно-ориентированный. Это просто не копия какого-то другого языка.
Основной принцип ООП — это то, что мы работем не с функциями и данными, а с данными, заключенными в объект ВМЕСТЕ с методами, которые эти данные обрабатывают.

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

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

Ну тогда ООП в лиспе и не ООП совсем.
Там мы работаем с функциями, только обобщёнными. И отдельно от них определяются данные — с помощью классов.

Оттого там, например, и диспетчеризация может быть более чем по 1-му параметру.
Основной принцип ООП — это то, что мы работем не с функциями и данными, а с данными, заключенными в объект ВМЕСТЕ с методами, которые эти данные обрабатывают.

Ну тогда все еще проще. Go прям православный ООП язык.
Например, в Python инкапсуляция отсутствует в принципе

Её «можно» организовать с помощью замыканий. Ну т.е. получить значение станет немного труднее. Потому что, видимо, в любом языке так или иначе можно-таки добраться до private- или спрятанных в замыкание значений.
Нет, определение ООП-языков не в этом.

В цитате — как раз об инкапсуляции. GO через расширение структур это делает.

Если обратиться за определением к википедии, мы обнаружим 2 понимания термина «инкапсуляция»:
In programming languages, encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination[1][2] thereof:

A language mechanism for restricting access to some of the object's components.[3][4]
A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.[5][6]


В русскоязычной литературе и ВУЗах принято второе, выделенное жирным.
Я где-то ниже дал ссылку на ooc. Почитайте.
Никто вам не мешает писать на C в oo-style.

А еще лучше сразу гляньте GTK. Там все объектное, хотя чистый C.
А разве из динамической либы, собранной из C++ можно дёргать классы через ffi? Там вроде как проблема была с виртуальными методами. В команде KDE специально писали libsmoke, чтобы можно было легко делать биндинги для других языков. В случае C всё было бы проще: дёргай ffi и всё.
Или сейчас уже нет проблем с динамическими либами на c++? А то я давно уже не писал на с++.
Так зачем использовать зоопарк языков, когда все можно писать на одном.

Развивая вашу мысль, и POSIX, и WinAPI также имеют C-интерфейс снаружи. Теперь все приложения на C писать? Нет, спасибо, я уже даже без буста не могу, не говоря уже о том чтобы к vanilla С вернуться.
Например, на хороший ИИ?

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

в крупных MMO, на сервер тоже может падать нагрузка, просчитывать поведение сотен тысяч MOBов.
Ну если только. Обычно все-таки задачу «предоставить игроку сильного интересного противника» предоставляют мультиплееру, а не пишут мегасложный ИИ для босса.
А причем тут мегабосс?
Обслуживание активного живого мира — сложная задача, поэтому большинство на нее забивает.

Это же вторая часть 3D, в котором не только «можно ли отсюда увидеть объект», но и «как отсюда пройти к нему, не застряв в текстурах», повторить для пары сотен объектов. Обычно делают уже готовые предрасчитанные пути. Из-за чего кардинальное изменение мира — не везде работает. Майнкрафт обошелся крупными кубиками и простейшим ИИ, а мог бы…
Немного странные рассуждения. Я, например, пишу в основном на C#, на C++ и C периодически, и портирую кучу всего из одного в другой. Дак вот для меня дико сложный C, нормальный C++ и совсем легкий C#. Ну в смысле, работы с сокетами и системой, многопоточностью, IPC и все такое. И код читать проще, и вообще все как-то тупее в реализации получается. Производительность наверное да, страдает. Но не так чтобы ужас ужас.
Крик души понятен.
Все родное, удобное и вдобавок быстро компилируется.
Замечу, что Турбо-Паскаль в 5 раз быстрее компилировался, чем Турбо-С.
Да.
Но всё равно его пришлось бросить.
Насчет пяти раз это вы поскромничали, кмк.
Такие игры, что на сайте у автора, хоть на асемблере пиши
Почему-то не упомянут Lua. А ведь можно взять SDL и lua-jit, в котором есть FFI. Весь код можно писать на lua, дёргая нужное из SDL через этот FFI. Ну и ещё всякие массивы объявлять как c-переменные (а то таблицы луа скоростью не блещут).
По скорости код будет немного уступать решению на С, зато (имхо) писать приятнее…
Ну тогда лучше сразу Haskell взять — богатая система типов, краткость, удобство… Но автор уже выкинул Go — потому что сборщик мусора.
Луа — небольшой простой язык, его интерпретатор довольно маленький, встраивается куда угодно и его можно уместить в APK.
Не знаю про хаскель, но разве там не требуется слинковать все хаскелевские модули статически, чтобы получить standalone-приложение? И соберётся ли всё это под нужную архитектуру? Скомпилить и запустить хаскелевскую программу можно, но судя по ответам на SO — это тот ещё гемморой.

А ещё плюсом подхода с луа является ещё то, что изменения возможно будет вносить без переустановки/перекомпиляции всего, достаточно на девайсе изменить скрипт. Скорость разработки увеличивается, отладка упрощается.
Стоп, мы ведь говорим о платформах с сишными библиотеками, как SDL? Да, соберется. И да, компиляция статическая, т.к. происходит проверка типов по всем модулям. Ну, а Lua не статически типизирован, и хотя, наверное, изредка это удобно — например, как вы выразились, подправить что-то на конечном дейвасе — в разработке от этого больше проблем. Чем больше проект, тем сложнее писать на динамическом языке, именно потому что все проблемы всплывают на конечном девайсе, куда это все еще надо загрузить и запустить ;)

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

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

На счёт того, что лучше, не буду спорить, всё равно каждый останется при своём мнении. Я лишь высказал свои идеи, которые меня как-то посетили.
Кстати, ведь SDL портирован под кучу платформ. LuaJIT написана на сях и тоже работает почти везде. И это навело меня как-то на безумную мысль, что на такой связке можно писать простенькие и при этом довольно шустрые игры почти для всех популярных платформ (кроме веба).

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

Получится что-то типа LÖVE (кстати, он теперь использует LuaJIT по-умолчанию), но проще.
кроме веба
emscripten, и можно и для веба ,))
Всё в порядке, в C нет деструкторов. Можете спокойно на нём писать. :)
Может, товарищу выше, наоборот, хочется деструкторов?
Ох уж эти деструктивные наклонности.
Вам стоит почитать ooc. https://www.cs.rit.edu/~ats/books/ooc.pdf
Но что толку от этого без RAII?
Да, С++ сложнее С.
Он настолько сложен, что ни один(или все такие есть такой?) компилятор не поддерживает его стандарт полностью.
Например, я пишу на С++ игры, но я некоторые возможности С++ не знаю и не изучаю. Например, я почти не ориентируюсь в шаблонах и без документации не смогу написать даже простого. Просто потому, что шаблоны практически не применяются при программировании игр.

ООП — это не серебряная пуля. Но, если код пишет человек без ООП головного мозга, то код получается нагляднее и чище, чем на plain C. Я уверен, что при прочих равных код игры на С++ будет проще и нагляднее.

Мне непонятен наезд на С++ про скорость компиляции. Современные процессоры и инкрементальная компиляция сделали компиляцию на С++ достаточно быстрой. Если же этого не хватает, то можно поместить исходники проекта на RAM диск и компиляция станет практически мгновенной.
ИМХО автор пишет огромные портянки в каждом CPP, пихает реализацию в H и в итоге, конечно, в медленной компиляции виноват С++.

P.S.
Я то вообще Delphi люблю. Но С++ стал стандартом игростроения не просто так.
C++ отвратительно медленно компилируется, вы легко можете в этом убедиться попробовав скомпилировать любой крупный C++-проект (OpenOffice, Chromium, UE4, kdelibs). Потому что в процессе происходит дублирование тонны информации в .obj-файлах. Вполне типична ситуация, когда .obj-файлы суммарно занимают, скажем, x100 места по сравнению с результирующим бинарником.
Ну так каков размер исходников Chromium'а? Клонирование всего дерева репозиториев запросто может быть медленнее, чем непосредственно сборка.
Ну например у нас тоже большой проект.
Экспорт кода из репозитория занимает несколько минут, а сборка всего проекта половину дня.
И при этом 8-ми ядерный i7 (4 реальных + 4 виртуальных) загружен на 100%.
То есть даже не в диск упирается.
Когда мне нужен ребилд — я просто ставлю его на ночь. А в процессе работы инкрементальная сборка сводит компиляцию к секундам.
Когда мне нужен ребилд — я просто ставлю его на ночь.


Ну в общем-то предлагаю на этом закончить, вы и сами признали, что C++ медленно компилируется.
Не надо демагогии.
Ребилд не является основной операцией в процессе разработки и скорость его не имеет принципиального значения.
Я хочу чтобы проект билдился в пределах единиц минут вне зависимости от того, какой файл понадобилось поменять. В противном случае, могут возникать всякие нездоровые мысли типа «не буду рефакторить этот кривой метод, потому что придётся поменять сигнатуру в H-файле и получить пересборку на два часа».
Давайте меньште абстрактных «два часа» и больше конкретики:
store.steampowered.com/app/380770 сессионная ММО на С++
Исходники одного только движка 16 мегабайт. Плюс еще столько же сама игра. Ребилд в 2015 году — около 10 минут.

store.steampowered.com/app/11560 РТС c терраформингом
Я не помню сколько там весили исходники. Использовался IncrediBuild. В 2006 году полный ребилд занимал меньше 30 минут.

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

А утром прийти и увидеть, что за ночь ничего не скомпилировалось, потому что таки допустил ошибку в коде. И всё, ещё один день впустую!
P.S. Я тут сижу просто потому, что у меня всё ещё компилируется. ;-)
Ошибку в интерфейса допустить сложнее чем в реализации. Да и компиляция прервется в самом начале, ведь если мы меняли хидер, то именно он будет скомпилирован первым и только потом «тысячи» файлов зависимостей от него.
Ну а изменение кода в CPP не приведет к перекомпиляции проекта.
Так что не принимается.
Да, обычно так и бывает, но не всегда.
Например во время рефакторинга, когда затрагивается несколько библиотек. К счастью, это редкость.
В любом случае, это не отменяет того факта, что компилятор С++ один из самых медленных по сравнению с другими популярными языками.
UFO just landed and posted this here
У вас просто маленький проект.
Мы пишем ОС, и компиляция с нуля идет около 8 часов на хорошем рабочем компе.
При помощи всяких ухищрений, icecc, sstate и др, добились около 2-3 часов.
Даже до 25 минут на компе с 32 ядрами и 128 гб оперативки.
Но до секунд не добраться никак. 80% времени — компиляция, 20% — дисковая подсистема.
Во-первых — при чем тут ОС? Речь о разработке игр.
Во-вторых я уже приводил пару примеров проектов в которых работал программистом, я бы не назвал их маленькими. :)
В-третьих — имеет смысл анализировать не ваши или мои проекты, а проекта автора.
пихает реализацию в H


C++ заставляет пихать реализацию темплейтов в H. И заставляет пихать объявления private-методов. При чем здесь автор, это язык такой.
И много шаблонов вам надо в геймдеве?
Я работал над несколькими действительно большими проектами и шаблоны встречал только на самом низком уровне движка. Они были раз реализованы и никогда не менялись.

Отдельно интересно, как автор решает проблему шаблонов в хидере в своих играх на plain C.
Отдельно интересно, как автор решает проблему шаблонов в хидере в своих играх на plain C.

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


В plain C нет проблемы шаблонов. Ну и вы как-то ловко проигнорировали вторую часть, про объявления private-методов в заголовках, проблемы с которыми в plain C кстати тоже нет.
Намека на шаблоны не достаточно?
Ладно, я намекну и на private: и как же автор решает проблему объявления приватных методов в plain C? :)
Не надо ООП тащить в pure vanilla C, и проблем не будет. Функция изначально приватная, если не сказано об обратном (extern).
Функция изначально приватная, если не сказано об обратном (extern).
Только с точностью наоборот (внешняя линковка по умолчанию, если не указано static), а extern для того, что объявлено «не здесь».
Да, что-то к пятнице мозги набекрень)
Нет, вы её легко можете дёрнуть из другого файла.
Вот если static пометили — тогда да, типа приватная.

xaizek опередил.
Может как в питоне, т.е. никак? Ответственность с разраба библиотеки переложить на плечи программиста, который её использует. Можно договориться о наименовании и вызывать «скрытые» методы только на свой страх и риск.

Так-то, private — тот ещё костыль, т.к. к нему при должном умении всё равно можно достучаться.
Если же этого не хватает, то можно поместить исходники проекта на RAM диск и компиляция станет практически мгновенной.

Ох ты ж… последний раз приходилось применять этот трюк, когда работал в 90х на 286 процессоре с 4 Мб памяти и 40 мегабайтным MFM диском. Компилятор Fortran а производства MS был дико медленен по сравнению с другими платформами. Только RAM диск и спасал.

А гляди ж ты — опять такой трюк работать будет.

Мда, как же здорово, что у нас проекты на Delphi с полным ребилдом собираются за 15-20 секунд. Миллионы строк.
Я RAM диск использовал для С++ проектов лет 6 назад последний раз.
С тех пор я открыл для себя инкрементальную компиляцию и перестал извращаться с RAM дисками.
Да, С++ сложнее С.


Как по мне, так наоборот.

Он настолько сложен, что ни один(или все такие есть такой?) компилятор не поддерживает его стандарт полностью.


В clang полностью реализован весь стандарт C++14. В gcc, вроде бы, тоже, не слежу за ним. И даже MSVC почти догнал.
А вот как раз C11 не реализован нигде целиком.

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


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

А насчёт скорости: в C++17 появятся модули, и тогда по скорости компилирования C++ догонит Паскаль (Дельфи).
Это именно вы их не применяете безотносительно области вашей разработки.

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

Опять ошибочная догадка. К примеру, писал Тулкиты и активно в них использовал шаблоны, например, для property.
В игра на высоком уровне шаблоны просто не нужны.
UFO just landed and posted this here
Шаблоны — это просто техника программирования, обобщённые алгоритмы.

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

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

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

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

Например, с теми же спрайтами. Есть общий класс Sprite. Есть наследуемые, например: Dragon, Knight и Princess (: public Sprite).

Наложение для всех сочетаний одинаковое. Реализуйте общий алгоритм наложения своим способом (ну, можно это сделать методом в Sprite).

А теперь специализируйте это наложение именно для наложения Knight на Dragon, допустим, а в остальном оставьте то же самое.
То есть для того чтобы сделать уникальную обработку для Knight/Dragon мы должны менять код в базовом шаблоне? Или я не правильно понял?
UFO just landed and posted this here
Как раз нет. Можно либо сделать template overloading, либо template specialization. Гуглите.

Если без шаблонов вы это будете специализировать, скорее всего, в конкретном классе (оверрайдом виртуальной функции), т.е. код может быть разбросан по куче разных файлов, то при использовании шаблонов всё будет рядышком. Красиво, чисто, удобно, а самое главное — нет грязи (возможных проблем) с указателями. И можно не делать виртуальную функцию (что накладывает, конечно же, свою печать на производительность).
По моему пример не очень. Тут лучше коллекции подойдут. Например у вас есть классы наследованные от «спрайта», есть классы наследованные от «звука», есть классы наследованные от «ресурса». И вам нужен мультиплеер или для ИИ нужно контролировать какие звуки и спрайты кто имеет чтобы рассчитать кто кого видит/слышит или передачу чего-то по сети, например. И вместо того чтобы устраивать множественное наследование всего от одного класса или городить интерфейсы вы делаете темплейт обертку/коллекцию которая помечает что кому принадлежит и может это все индексировать и хранить с хэшем. И потом создаете для звуков Обертка<Звук>, для спрайтов Обертка<Спрайт> в коде, и у вас одна имплементация «контроля принадлежности» вне зависимости от того объекты какого класса вы решили контролировать.
Я пытался упростить уж. И показать, что даже на примере одного вида объектов (тупо спрайтов) шаблоны очень помогают. Чего уж говорить о взаимодействии кучи разных иерархий.
Ну шаблоны в простых ситуациях слабо помогают, зато сегодня у меня вот такой кадавр родился:
    public sealed class MarketDataStream<T,X,Y> : IDisposable, ILoggerAttached, INamed 
        where X: IStreamHeader 
        where Y: IStreamFeedbackHeader
А какая разница, я могу ошибаться, но можно так:
template <class T, IStreamHeader X, IStreamFeedbackHeader Y>
class MarketDataStream : public IDisposable, public ILoggerAttached, public INamed { }

Я не так хорошо знаю С++ правда, если есть ошибки, скажите, мне самому интересно
Так делать нельзя (явно передавать типы в параметрах шаблона), non-typed parameters могут быть только int'ы и т.п.

Бида, бида. Вобщем слава богу я всякий UI на C# пишу. На С++ у меня только обертки всякого хардкора и порты С библиотек.
UFO just landed and posted this here
Ясно, это я еще помню как override в С++ раньше не было, тоже удивлен был.
IStreamHeader и IStreamFeedbackHeader интерфейсы struct, т.е. в С++ тоже не получится?
В C++ struct и class — одно и то же, нюансы в видимости по дефолту.

Я так понял, вам нужно, чтобы был передан объект, который реализует определённый интерфейс?
Два варианта: как сказал выше 0xd34df00d, либо просто передавать их в конструктор (а смысл шаблониться от конкретных объектов? Эдак у вас на каждый объект будет разворачиваться шаблон, отсюда и требование.)
Ну там чуть посложнее, там передается поток данных, а в начале memorymap два заголовка, чтобы не локать треды. Передатчик и приемник знают какие структуры они пишут, а обертке потока нужно только пара параметров которые есть во всех заголовках. В итоге все структуры заголовков унаследованы от
    public interface IStreamHeader
    {
        Guid Tag { get; set; }
        long Size { get; set; }
        long Heartbeat { get; set; }
        int DataOffset { get; set; }
        bool Choked { get; set; }
    }

Размер структуры в заголовке чтобы считать смещение следующей, ну и т.д. В итоге указание интерфейса структур в шаблоне это просто страховка, можно и не указывать на самом деле.
Так в C++ делайте в конструкторе, помимо того что невозможно — ещё и незачем указывать их как параметры шаблона.
Т.е. вам надо:

template <class T>
class MarketDataStream : public IDisposable, public ILoggerAttached, public INamed 
{
    // конструктор 
    MarketDataStream(IStreamHeader X, IStreamFeedbackHeader Y)
    {
        // ... 
    }
}
Ясно, это я еще помню как override в С++ раньше не было, тоже удивлен был.


оверрайдить-то всегда можно было, просто не было явного ключевого слова.
Ну в C# и Java указывать override это mandatory. Я в смысле что ООП в С++ оно вольное какое-то. Я до сих пор в восторге от Питона, правда не пишу на нем сейчас практически.
Ну не сравнивайте язык, в котором ООП был спроектирован изначально (C#) гением (создателем Delphi) и где, в принципе, иногда забивают на обратную совместимость (это правильно, кстати), с языком, в котором вынуждены обеспечивать обратную совместимость из-за тех, кого я называю гуру-старпёрами.
UFO just landed and posted this here
UFO just landed and posted this here
Формально реализован, по факту встречаются баги сильно чаще, чем в clang. Причём, чаще это мешающие баги — clang скорее акцептит невалидный код, тогда как gcc реджектит валидный.


Не знаю, мне gcc не нравится по скорости компиляции, да и в clang аналитики больше, те же санитайзеры чего стоят. Кстати, есть пример акцепта clang'ом невалидного кода?

Как у них там с экспортом темплейтов из модулей, кстати?


Я не слежу, в clang пока не появилось.
UFO just landed and posted this here
Я какой-то то ли пропозал, то ли обсуждение читал, и там как-то всё плохо с темплейтами.


Фиг его знает, что будет, но одним из недостатков include'ов авторы разных пропозалов о модулях заявляют как раз неудобство реализации шаблонов в инклюдах. Вчитываться вообще нет времени…
Кармак тоже раньше писал свои движки на C. Но начиная с третьего Дума таки перешёл на С++. Итересно почему?
Может быть потому, что для написания третьего Дума пришлось набирать много новых разработчиков, а программистов на C искать все сложнее с каждым годом.

Хорошо еще на C++, а то сейчас в резюме чаще указывается знание неизвестного мне языка C/C++, а вот знание C или C++ теперь встречается все реже.
Сфера применения знака / (косая черта) — научная и деловая речь. Он употребляется в следующих функциях.
1. В функции, близкой к союзам и и или, как знак альтернативности понятий или обозначения единого сложного понятия.
Это неприменимо при указании названия языков. Например, язык C++/CLI является отдельным языком и имеет свой стандарт — ECMA-372.

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

Почему? Я бы с вами согласился, если бы был язык C/C++, но него нет. Так что нет противопоказаний для такого употребления.
Согласно Вашему же комментарию выше, C/C++ следует трактовать одним из следующих способов:
  • C и C++
  • C или C++
Теперь посмотрите на оба и подумайте об их применимости. Если это вакансия или резюме, то знание «C или C++» звучит как минимум бредово, но скорее абсолютно некорректно. С вариантом «C и C++» другая проблема, на удивление мало людей действительно знает их как два самостоятельных языка: знающие C зачастую не знают практически ничего из C++, знающие C++ (по моему опыту) думают, что в C есть ссылки, нету typedef, без понятия о функциях вроде strspn(), не знают отличий приведения типов и т.д.

Т.е. приходим к выводу, что применимость обозначения «C/C++» настолько узкая, что оно практически всегда применяется неправильно. А если ещё и учесть двоякость трактовки («и» или «или»), то смысл использования такой записи вообще теряется, так как не понятно, что хотели сказать.
Я С/C++ всегда воспринимаю так: я пишу на С++ и знаю чем он отличается от С.
Первый раз втречаю такую трактовку и не сказал бы, что мог бы предположить её существование. Для меня это лишний раз показывает ошибочность практики применения записи «C/C++».
UFO just landed and posted this here
Язык все-таки C++. А вы говорите уже про разные версии стандарта. Например, ISO/IEC 14882:2011(E) — это стандарт языка C++, который определяет, что «C++ is a general purpose programming language». Тоже самое написано и в других версиях стандарта.
UFO just landed and posted this here
С тем, что лучше указывать свой опыт работы с конкретной версий языка, согласен. Тут даже можно добавить с какой версией предпочитает работать человек, чтобы потом не было взаимных обид и разочарований.
www.linux.org.ru/forum/talks/8720321
Как сообщает похороникс, Джон Кармак высказал своё окончательное мнение о противостоянии C и C++. Для обделённых знанием английского или пониманием божественности плюсов перевожу:

Я до сих пор считаю код Quake 3 более чистым — в известном смысле. Это венец эволюции моего стиля на C, и одновременно первая итерация моего стиля на C++. Но такое отношение может быть вызвано меньшим количеством строк или тем фактом, что я не особо-то и не заглядывал туда уже десять лет. Я думаю, что «хороший C++» лучше, чем «хороший C» с точки зрения читаемости, а всё остальное эквивалентно.

Я, конечно, поизвращался над C++ в Doom 3 — ведь я был опытным C-шным программистом с некоторым знанием ООП, пришедшим из Objective-C времён NeXT, так что я просто начал писать на C++ без нормального изучения юзкейзов и идиом. Вспоминая прошлое, я очень жалею, что не прочитал Effective C++ и некоторые другие книги. Часть остальной команды в прошлом имела опыт работы с C++, но они в целом следовали моим стилистическим решениям.

В течение многих лет я не доверял обобщённому программированию, и все ещё применяю шаблоны с опаской, но в итоге я решил, что удовольствие от статической типизации перевешивает нежелание иметь раздутый код в заголовках. В Id Software всё ещё идут споры об использовании STL, и со временем они становятся жарче. Если же вернуться снова к временам, когда началась разработка Doom 3, призыв использовать STL тогда стал бы неудачной затеей, но сегодня в пользу этого решения уже есть серьёзные аргументы, в том числе в геймдеве.

Также я теперь стал const-нацистом, и даю по рукам программисту, который не ставит const подходящим для этого переменным и параметрам.

Главным нововведением, интересным для меня, является функциональное программирование — оно позволяет избавиться от многих старых привычек и вновь отказаться от некоторых идей ООП.
Кстати по поводу const — любопытный факт, но далеко не всегда в ситуациях при оптимизации, где, казалось бы, компилятор должен догадаться о неизменяемости, он действительно догадывается. Помню, пол-года назад мне надо было оптимизировать прогу под встраиваемое устройство. Можно было переписать часть функций, превратив их в трудноотлаживаемую мешанину, но по опыту в будущем ни к чему хорошему это бы не привело. Поэтому — помимо игр с опциями оптимизаций компилятора — огромная часть проделанной работы оказалась простым путешествием по всем замешанным функциям кода, и заменой переменных и аргументов на const везде, где только можно. Работала такая оптимизация прежде всего со всякими векторами и структурами. Для проверки, сработало ли в очередной раз, достаточно перекомпилировать один объектный файл с оптимизациями — и, если размер уменьшился, следовательно компилятор смог применить еще какую-то оптимизацию.
Может, и так, но «const-нацизм» используется в первую очередь для того, чтобы оградить себя от случайной модификации данных (будь то аргументы или локальные переменные), и уже во вторую очередь — для оптимизации. D C++ это работает так себе, поэтому в D пошли ещё дальше, и сделали const транзитивным:
void fn(const int *x)
{
    int y;
    x = &y; // error
    *x = y; // error
}

Вот так в D объявляется изменяемый указатель на неизменяемый int:
const(int) *x

Если указатель константный, то и данные, доступные через него — тоже. Вот это настоящий const-нацизм.
Я в курсе, но в D const int* x по умолчанию не даёт выстрелить в ногу, а C++ вариант const int* const x выглядит уже не так опрятно. В C++ вообще много чего есть, но есть языки, в которых это сделано лучше.
const и restrict дают очень много в плане оптимизации, потому что любая модификация памяти через указатель должна сбрасывать все закешированые ранее в регистрах значения. В C++ для этого есть свои достаточно специфичные правила (перечитываются только значения того типа, который изменился по указателю). Кто-то даже шутил, что использование restrict при реализации математических методов позволяет C по скорости догнать FORTRAN.

Если брать const как защиту от случайной модификации, то теоретически да. Но на практике я больше вспоминаю случаи, когда погорячился с const, и указатель надо было вернуть в неконстантное состояние, часто по длинной цепочке, иногда с пересмотром архитектуры. А вот когда бы это действительно помогло… не помню… Может быть, но уверенности нет.
Это как парадокс, что пользователи чаще пишут отзыв при плохом происшествии; когда же все идет отлично, они молчат — люди просто не обращают внимание, что все хорошо. Я могу вам назвать, когда const действительно полезен — прежде всего при отладке. Как только у вас что-то идет не так, а особенно в трудностях локализации ошибки — const в аргументах и теле позволяют сразу сказать: «Ага, я не модифицировал это значение, значит ошибка не тут!». В частности может помочь, когда переполнение буфера (да, я в курсе про address sanitizer, но предположим его у вас нет), и все выглядит, словно изменяется именно эта переменная.
Вот и Линус Торвальдс такой же, не понимает он C++ — и все вынуждены писать в ядре на Си.
В курсе. Поэтому предпринимаются попытки его стандартизировать. Просто не было необходимости.
UFO just landed and posted this here
> ООП… почему надо так жёстко объединять код и данные.

Кхм… Я не понял связи между ООП и объединением кода с данными.

Посмотрел игры автора, половину из них можно было написать на JavaScript или вообще на HTML5. Только зря любимую сишку насиловал…

Раз такое пропустили на Хабр (и ведь даже не на Гиктаймс, а на Хабр!), может и мне стоит написать статью, как игру за три дня написал. Пусть мне уже и объяснили, что три дня это довольно много.)
«Я пишу на голом Си» теперь для меня заиграло новыми красками!
Самый обстоятельный, убедительный и отлично написанный разбор того, почему C в сто раз лучше чем C++ — C++ Frequently Questioned Answers. Вот бы ещё кто-нибудь совершил подвиг и на русский язык это перевёл.
UFO just landed and posted this here
Автор просто плохо знает язык.
Многие вещи субъективны, многие откровенно ошибочны, многие устарели в связи с выходом 11-го и 14-го стандартов или стремительно устаревают в связи с подходом 17-го.

Пункт насчёт скорости компиляции не совсем понятен — вряд ли C компилируется быстрее чем, например, C# или Java.
Кстати, раз упомянули разработчиков в области GameDev, творящих на Си: слежу за этим гражданином — www.twitch.tv/quel_solaar/profile
И уж поверьте — уровень графики в его текущем проекте просто превосходный!
Ну уж графика-то в современном мире от языка вообще не зависит.
Так это я, как бы, в ответ комментаторам выше, которые говорят, что у автора первоисточника статьи «графика 90х» в играх.
Уровень графики зависит от самой графики — шейдеров и арта. Написан движок на С или C# уже имеет малое значение, потому что современные проекты упираются в GPU в подавляющем большинстве случаев. Взять тот же Unity на mono. Когда же тебе важна CPU производительность, то выбор практически всегда С++. Банально, настолько сложные архитектурно проекты сложно писать на С и С++ не случайно является де-факто стандартом в геймдеве.
В качестве альтернатив Rust даже не упоменается. Автор про него не слышал?
Хотя Rust все-таки немного сложнее C, эта сложность оправдана — ни чего лишнего там нет.
Я бы сказал, что он даже сложнее C++
В C++ очень много неочевыдных подводных камней, типа невиртуальных деструкторов или изменение класса объекта в процессе работы конструкторов.
В Rust же таких неожиданностей нет. Да и сложных концепций, типа раследования.
Владение и заимствование — достаточно сложная и непривычная вещь, но она проще, чем «умные указатели».
Так что в целом Rust на порядок проще.
Зная C++, конечно, Rust покажется лёгким ;-)
А знание Rust при изучении C++ сильно поможет?
Я пока не встречал людей, которые изучали в этом порядке :-) Но конечно же поможет.

Это людям с языками с GC в бэкграунде трудно изучать C++ или Rust.
UFO just landed and posted this here
Это большая свобода и большая ответственность, но уж лучше так, чем сражаться с языком когда доходишь до предела его возможностей и натыкаешься на невидимую стену из багов или ограничений производительности.
Лучше я сам буду рулить памятью чем сборщик мусора будет фризить мою прогу когда ему вздумается, и мне виднее когда память выделять а когда освобождать.
В случае Си мне всегда понятно что оно делает и как, никаких махинаций за ширмой, никакого волшебства, нюансы конечно есть но их довольно мало в сравнении с языками которые на себя берут заметно больше.

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

Низкий уровень Си является огромным его минусом и всегда таким будет. Минусом это перестает быть только тогда, когда задача требует этого. Именно требует. Ядро ОС требует. Драйвера требуют. Игры — игры автора никаким местом не требуют. Это просто прихоть автора, ничем объективно не обоснованная. А вот игры ААА уровня, особенно для консолей — требуют. Но там Си никому не нужен, используют С++. Последний, собственно, нормально подойдет и для ОС с драйверами. Например, Apple для драйверов выбрала С++ и ООП интерфейс developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/Features/Features.html#//apple_ref/doc/uid/TP0000012-TPXREF101

Что до линукса, то смотря на их забавные попытки, по сути, реализовать C++ на Си в той же VFS, то там тоже не видно причин не использовать С++. Просто Линус против, да и время сыграло свою роль. В то время компиляторы вполне возможно могли быть не так хороши.
Что до линукса, то смотря на их забавные попытки, по сути, реализовать C++ на Си в той же VFS, то там тоже не видно причин не использовать С++
А зачем там плюсы? Всё равно rtti и exceptions придётся отключать для ядра, плюс развлечения по борьбе с mangling, что приведёт код к Си-подобному. Хотя шаблоны были бы крайне приятны.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Спасибо за перевод, прочитал с интересом пост и особенно комментарии. Наверно автор мог бы сократить свой пост до строк: я пишу на ванильном С, потому что не смог до конца освоить ничего более сложного. Но в этом случае его пост бы не перевели для хабра.
Sign up to leave a comment.

Articles