Список вопросов
Как зайти в Даркнет?!
25th January, 01:11
8
0
Как в tkinter из поля ввода Entry получить значение в одну переменную и обновить строку кнопкой, затем получить ещё одно введённое значение и затем сложить их. Ниже пример кода
21st July, 19:00
899
0
Программа, которая создает фейковые сервера в поиске игровых серверов CS 1.6 Steam
21st March, 17:43
952
0
Очень долго работает Update запрос Oracle
27th January, 09:58
916
0
не могу запустить сервер на tomcat HTTP Status 404 – Not Found
21st January, 18:02
907
0
Где можно найти фрилансера для выполнения поступающих задач, на постоянной основе?
2nd December, 09:48
942
0
Разработка мобильной кроссплатформенной военной игры
16th July, 17:57
1727
0
период по дням
25th October, 10:44
3957
0
Пишу скрипты для BAS только на запросах
16th September, 02:42
3722
0
Некорректный скрипт для закрытия блока
14th April, 18:33
4614
0
прокидывать exception в блоках try-catch JAVA
11th March, 21:11
4382
0
Помогите пожалуйста решить задачи
24th November, 23:53
6087
0
Не понимаю почему не открывается детальное описание продукта
11th November, 11:51
4352
0
Нужно решить задачу по программированию на массивы
27th October, 18:01
4400
0
Метода Крамера С++
23rd October, 11:55
4309
0
помогите решить задачу на C++
22nd October, 17:31
4002
0
Помогите решить задачу на python с codeforces
22nd October, 11:11
4492
0
Python с нуля: полное руководство для начинающих
18th June, 13:58
2599
0
Разобраться со слабыми сторонами C++?
Просмотров: 325
 
Ответов: 6
Попробуем все разложить по полочкам:
70-e: Успех Си.
80-90-е: Программное обеспечение стало сложнее. Чтобы не сильно усложнять процесс разработки, понадобились новые абстракции:
- наследование
- приведение типов
- перегрузка операторов
- шаблоны
- исключения
кроме того, не хотелось потерять в производительности.
Появился C++ — язык не только системного, но и прикладного программирования. Со временем стало ясно, что это не лучшее сочетание: небезопасная арифметика указателей и макросы хорошо подходили для низкоуровневого программирования, но на высоком уровне легко приводили к ошибкам.
Оказалось, что:
- множественное наследование сложно использовать
- перегрузка через virtual неудобна
- исключения трудно реализовать в компиляторе, поэтому если и речь идет о многоплатформенности, то лучше о них забыть
- шаблоны — не самый простой способ генеративного программирования
Всё это известно, но только в виде таких вот односложных утверждений, без исследований и живых примеров. Было бы здорово, если бы такие примеры нашлись. Сам я не занимаюсь профессионально разработкой на C++, поэтому обращаюсь за сим к хабрасообществу
Что я могу сказать про проблемы C++?
1. Слишком слабая типизация. Например, int x = 0.0;
2. Система хедерных файлов крайне медленна, «предкомпилированные хедеры» и extern template — полумеры.
3. Запутано подключение чужого откомпилированного кода (DLL, к примеру). Мало написать хедер, надо ещё откомпилировать lib — в общем, интересного мало.
4. Библиотека STL крайне жирна. Хотя и libc тоже «хороша» — минимальная программа на Паскале занимала несколько килобайт, в зависимости от компилятора, на Си — приближается к сотне килобайт. Я не говорю про Linux/MSVC, где libc динамически подключаемая.
5. Строковый литерал на C++ — это та же нуль-терминированная строка. Когда эту строку приходится оборачивать в какой-нибудь std::string, уже при выполнении вычисляется её длина. Зачем? Почему бы не вкомпилировать её в exe'шник?
6. Нет ключевых слов override/reintroduce. При изменении сигнатуры виртуального метода приходится вспоминать, где он переопределялся.
7. Нет виртуальных конструкторов. «Фабрика» — полумера.
8. Коряво реализовано право доступа «читай кто угодно, пишу только я».
9. Явное определение методов как inline или не-inline в сочетании с шаблонами приводит к странным эффектам. Когда расшаблонивание приводит к сложному коду, inline вреден (сжирает кэш процессора), когда к простенькой операции с указателем — наоборот, нужен. В общем, это давно уже должно стать парафией оптимизатора.
10. В разного рода callback'ах замыкание приходится реализовывать собственными силами. Что-то типа: typedef void (*ProcDoSomething)(int aParam, void* aClosure). То же самое в Delphi: type ProcDoSomething = procedure(int aParam) of object;
11. Если вдруг случайно два разных модуля реализуют одно и то же, но один препроцессором, а второй — синтаксисом C++, будет ОЧЕНЬ много геморроя с поиском ошибки.
12. В обычном цикле for счётчик упоминается трижды. В общем, место очень ошибкоопасное. Для самых простых циклов у меня вообще есть макрос FOR_S (i, 0, n); суффикс S означает size_t.
13. Когда из-за рефакторинга «внутренней кухни» объекта меняется способ хранения ссылки, меняется и код, который этой ссылкой пользуется. Например: object.buddy.field, object->buddy.field, object.buddy().field — в зависимости от того, buddy реализовано как Buddy& buddy, Buddy* buddy или Buddy buddy().
Пока, засиделся. Мне бежать.
>Со временем стало ясно, что это не лучшее сочетание: небезопасная арифметика указателей и макросы хорошо подходили для низкоуровневого программирования, но на высоком уровне легко приводили к ошибкам.
Кто вас просит ими пользоваться? Я вот не пользуюсь (точнее оно нужно в 1% случаев).
>исключения трудно реализовать в компиляторе, поэтому если и речь идет о многоплатформенности, то лучше о них забыть
Скажем так, нет гарантии, что они безопасны. Второе, они таки тормозные. В третьих реально они нужны в очень редких случаях.
И на чем вы изволите писать большие проги, типа MSOffice? Он между прочим на плюсах полностью написан и поэтому работает очень быстро. На чем писать софт для мобилок, где проблемы с ресурсами стоят остро?
А многие проблемы с плюсами не столь серьезны, как любят многие говорить, а еще больше проблем возникает из за кривого проектирования фреймворков для плюсов. За примером ходить не надо — MFC, который и С++ фреймворком рука не поднимается назвать.
> множественное наследование сложно использовать
Простой пhимер: А имеет два подкласса: B и C, от них происходит D.
Если родительские классы имеют общего предка (A), то в потомке (D) будет несколько экземпляров этого предка. С каким будут работать унаследованные методы в общем случае неизвестно. При приведении указателя D* к A* также непонятно, на что он будет указывать.
Это можно исправить, объявив наследование от A, как virtual в B и C, но работать это будет значительно медленнее и инициализацию такого предка придётся делать в каждом потомке (B, C, D и далее по иерархии), а не только в непосредственных B и C. Это не только неудобно, но и не вписывается в ООП. Кроме того, если A,B и C объявлены в сторонней библиотеке, такая операция вообще невозможна.
> перегрузка через virtual неудобна
Не могу обосновать. Я не считаю её неудобной. Мне всегда нравилось.
> исключения трудно реализовать в компиляторе, поэтому если и речь идет о многоплатформенности, то лучше о них забыть
Забыть о них не получится, т.к. они используются в языке (оператор new, например, их использует). Другое дело, что мне, например, они неудобны, но я вообще склонен избегать выходов управления и середины функции, как, извините за грубость, оператора goto. В ряде случаев, впрочем, использую.
> шаблоны — не самый простой способ генеративного программирования
По моему, он достаточно прост. Может быть, даже слишком: с помощью шаблонов очень легко раздуть программу массой однотипного кода.
Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться