Pull to refresh

Comments 77

У вас проблема с названием статьи. Статью надо было назвать по другому. А именно «Почему С++ — отстой и чем его заменить».

Не реализован класс массив? Нету нормальной мапы? А вы точно про С++?
Добро пожаловать в клуб самоубийц… нашел, блин, с чем на C++ наезжать
Но ведь… автор из песочницы… его пригласило НЛО…
Если Вы привыкли, что Вас должен язык вести за ручку по достаточно очевидным (в контексте написания чего-то сложнее лабораторных работ в универе) вещам и бить по рукам за то, что Вы не можете следовать элементарному Code Style — это, в первую очередь, Ваша проблема.
И уж тем более не проблема языка в том, что Вы не смогли понять работу стека и не нашли, что «умный» массив в C++ называется vector, а также не приметили «словарь» и «карту» (хотя про set и map написано, наверное, в каждом учебнике по C++).
C++ предполагает, что разработчик достаточно умный, чтобы не играться с преобразованием типов без полного понимания всех последствий, и чтобы в случае объявления аргумента-ссылки явно указывать «const» при необходимости. Ну, и к тому, что если метод принимал аргумент по значению, не стоит менять его на принимающий значения по ссылке без предварительного согласования всего вызывающего этот метод кода.
По поводу const и способа их изменить через пень-колоду: const и прочие ограничения придуманы не для спасения от самого себя, а для спасения от законов Мёрфи.
>> нельзя создать функцию/переменную/константу вне класса (ведь на любые действия или данные можно найти класс)
Да, но всех настолько достало постоянно писать Math.sin(), что в C# 6.0 ввели статические импорты.
Я так мечтал об этой фиче… Но как только она появилась понял, что она слишком непривычна.
Коллега сделал класс, где достаточно часто мелькали
if(IsNullOrWhiteSpace(arg))
После прочтения пришло осознание, что
if(String.IsNullOrWhiteSpace(arg))
гораздо читабельнее.
Хотя математикам проще, да.
вот, кстати, есть как минимум одно исследование, которое говорит, что_underscore_нотация_иди_snake_case_позволяет_легче_читать_код: ieeexplore.ieee.org/xpl/articleDetails.jsp?reload=true&tp=&arnumber=5521745

Ваш пример в snake_case:
if (is_null_or_white_space(arg))


я же придерживаюсь внутреннего Style Guide по которому snake_case используется для свободных функций.
Google использует snake_case для функций, которые выполняются быстро, а CamelCase — для функций, выполняющихся долго и медленно. Тоже интересный подход.
Речь шла не про Style Guide, а про импорт статических классов.

Style guide-a в идеале придерживаются все участники проекта, независимо от личных предпочтений.

Импорт статики — дело (пока) сугубо добровольное, хотя R# и подталкивает пользоваться фичей. Проблема в том, что читать код с импортированными статическими классами непривычно.
Рассмотрим стандартные библиотеки. String.IsNull() и IsNull(), File.ReadAllBytes() и ReadAllBytes, Path.GetFileName() и GetFileName() — методы в любой из этих пар мне кажутся разными, читаемость падает, поэтому я не импортирую String, Path, File классы. В своих библиотеках я предпочитаю extension методы, и импорт статики ну нужен. В итоге фича применима только для ограниченного числа статических классов вроде Math, поскольку при его импорте читаемость кода не ухудшится.

При этом года через три этой фичей будут пользоваться гораздо активнее (в силу стандарта R#), и тогда привычки придется поменять.
Ещё раздражает когда this. не пишут.
А за Point Point я пока не придумал как поквитаться.
Язык программирования — это инструмент. Он не должен вас ничему учить и ничего вам говорить, а наоборот: он предоставляет свой набор возможностей, и вы решаете, что из них вам нужно. Если язык позволяет больше, чем вам нужно (например, «Что можно именовать типы, методы и переменные маленькими буквами»), вы просто не используете эти возможности. Это плюс, а не минус.
Тут самое главное, чтобы язык грамотно давал по рукам, когда хочется сделать чего-то «просто так», не подумав, но в то же время позволял извернуться в любой ситуации без излишнего brainfuck-ства. Так что в чем-то концепции шарпа правильные (особенно с приведением типов, впрочем, так во многих молодых и не очень (Java) языках).
Нытье человека, освоившего C#, но которому лид дал задание что-то написать на C++
Я подозреваю, что автор ещё не дорос до работы, уж очень школьный стиль опубликованного «анализа».
Студент, или недавний студент. Недавно принят на работу. Месяц кодил тулзу для внутренних нужд с интерфейсом на DevExpress. Прилетел таск на основной код на C++, с которым знаком только в теории. Практика ужаснула.
Я сейчас описал реальный случай.
Повезло мне, что нам в университете преподавали плюсы. Не пишу на них уже лет пять-семь и не очень тянет, но уважение с тех пор осталось )
Школьник освоил C# (ибо программист старой школы мельком знает про C/C++ и не обсирает почем зря).
Всю жизнь прожил по типу
C# нам говорит: программируйте в удовольствие, у меня для этого всё есть: замечательные правила языка, правила именования, а также разные полезные библиотеки.

И вдруг понадобилось подключить какую-то библиотеку или SDK на древнючем C++!!!… и тут мир перевернулся.
Не стоит так обсирать сей замечательнейший язык.
Вы даже не представляете какой нирваны можно можно достигнуть по пути C/C++.
Образцово-показательный хабрасуицид. Ждём дальнейших откровений на тему «почему виндовс мастдай», «mongodb лучше, чем mysql» ну и что-то на тему почему тёплое более круглое, чем мягкое.
По-моему этот пост уже переплюнул упомянутые вами темы )
В чём вообще смысл таких сравнений? Шарп и плюсы — языки под разные задачи. На C# вы при всем желании не напишите, скажем, операционную систему. Инструмент правильный подобрать настоящий джедай должен.
Справедливости ради, нужно отметить Cosmos, SharpOS и Singularity :-) Но…
Точнее, это Visual Studio Style, если не ошибаюсь. У C# разные имплементации, и стиль тоже отличается (от рекомендованного в Mono, допустим).
Сравнение конечно не самое лучшее и многое о чем автор позабыл (и дремучие ошибки компиляции темплейтов и сама скорость компиляции и вообще инструменты отладки кода (увы, это относится и к языку, т.к. сделать для C++ аналогичные удобные инструменты малореально), и сахарные возможности C# вроде оператора?.. и многое-многое другое) Однако автор мыслит абсолютно прагматично. И я бы сделала такой вывод: при создании нового проекта где нет жестких требований вроде переносимости под какие-то редкие платформы и/или полное отсутствие рантайма и сборщика памяти или необходимости тесного взаймодействия с другими С++ библиотеками, то писать такое приложение на С++ — чистое безумие.
Вот это по нашему. Ни одного слова про CLI и рантайм. Вы уж определитесь что сравниваете.
Раз в пару месяцев появляется (пролазит в обсуждаемое) такая статейка. И главное, почти все время C++ и C#, нет бы Javascript и LISP или Visual Basic и Пролог. C++ народ защищает, С# не очень, видимо сказывается меньшая популярность. И конечно, любая такая статья неполна без пары мудрых комментариев что для каждой задачи свой инструмент.
Просто идиотизм статьи зашкаливает. Есть С# у которго свой рантайм и С++ который компилируется в «нормальный» бинарник. Есть С++ CLI который вроде как С++11, но там таблица с расхождениями от стандарта на 10 листов А4, есть managed C++ — кадавр для рантайма .NET, есть еще куча тонкостей. Дак либо сравнивайте сам синтаксис языков и не пихайте в сравнение стандартные бибилитеки. Либо сравнивайте CLR и С++ — «какая удобная стандартная библиотека». Есть еще другие варианты, а тут бред на уровне «крокодил больше круглый чем зеленый, щас я вам это докажу!». Нет бы тиснуть статью что например в C# override был уже давно, а в С++11 появился впервые и о чудо, это поддерживается реализацией MSVS начиная с 2010, с минимальным отходом от стандарта.
>Есть С++ CLI который вроде как С++11, но там таблица с расхождениями от стандарта на 10 листов А4
C++/CLI — это не C++11, это набор расширений для оного, чтобы из нативного C++ кода можно было взаимодействовать с управляемым, а из управляемого — с нативным. Другими словами, C++/CLI — это надмножество C++.

>есть managed C++
Это старая версия C++/CLI.
Именно. Просто я на C# в основном пишу, а в вариантах С++ путаюсь постоянно. С и С++ я по большей части через вообще через nmake собираю. Больших проектов на них у меня нет, в итоге мне понятнее получается.
С++ позволительно критиковать только двумя способами:

1) C++ N лучше в указанных вопросах, чем бардак C++ N-1. И это интересная и полезная критика.

2) C++ применительно к специфической задаче хуже, чем _insert_language_name_here_. И это бестолковая публичная критика, позволенная только в узких кругах, когда junior хочет применить слишком мощный язык к узкой задаче.

В остальных случаях это holy-war и flame, иногда с утерей кармы.
Пример с константным указателем сделал мой день в стиле «я докажу вам что ваш язык отстой, потому что я не умею им пользоваться».
Видно, что у него и с C# то плохо, особенно повеселил пример с «макросами».
Тем более, что в шарпе в последних версиях разрешили не писать ref и out при вызове. На мой взгляд, зря, это как раз полезно было понимать, что аргумент может/должен поменятся.
У меня до сих пор требует, что, тем не менее, не огорчает :)
#include <iostream>
#define profit int main(int argc, char *argv[]) {std::cout << "MACROS MASTER!!!";  return 0; }
profit

Шах и мат, Автор!
Вы о соглашениях именования хоть немного слышали?
Они не ограничиваются только Pascal style — https://msdn.microsoft.com/ru-ru/library/ms229043.aspx
Ну а все остальные «доводы» просто смешны.
К вашему сведению уже давно есть C++11, C++14 с строгими перечислениями (enum class) и умными указателями.
Не нужно смешивать C++ и C# — первый это продолжатель Си, второй был создан как помесь Delphi с синтаксисом Java что бы и вы могли «программировать».
Выделение памяти (new/malloc) жутко тормозит! Вы сами, сами! должны делать управлятель памятью (ничего себе задача!), либо довольствоваться медленной работой


Так в C# выделение тоже тормозит, просто это делается, например, при запуске.
Открою секрет: в C# выделение памяти происходит точно так же через malloc (желающие могут погуглить точное название функции из WinAPI), как и в любой операционной системе.
Ну и это претензия не к собственно языку, а к инфраструктуре.

На C++/CLI всё так же, как в C#.
Нет, в C# быстрее.
Выделение объекта с полем 1 байт в 3.736 быстрее, а выделение массива 100 байт в 3.004 раз быстрее.
Покажите методику расчёта. Логически подумайте, в C++ это те же самые вызовы WinAPI.

Уверен, ошибка либо в методах измерений, либо в C# вы «выделяете» память со стека, а в C++ — с кучи.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PerfomanceTests {
	public class PerfomanceTest {
		public delegate void PerfomanceTestAction();
		String title;
		PerfomanceTestAction action;
		int count;
		
		public PerfomanceTest(String title, PerfomanceTestAction action, int count) {
			this.action = action;
			this.count = count;
			this.title = title;
		}
		
		public long MeasurePerfomance() {
			Stopwatch watch = new Stopwatch();
			watch.Start();
			int i = 0;
			LABEL:
				action();
				i++;
			if(i < count) goto LABEL;
			watch.Stop();
			return watch.ElapsedMilliseconds;
		}		

		public void Run() {
			WriteLine("Perfomance of "+title +":  " + MeasurePerfomance()+" ms");
		}
	}
	
	class Program {
		class T {
			byte c;
		}

		static void Main(string[] args) {
			new PerfomanceTest("MAlloc", PFMAlloc, 10000000).Run();
			Console.WriteLine("End!");
			Console.ReadLine();
		}
				
		static byte[] x;
		static void PFMAlloc() {
			x = new byte[100];
			//new T();
		}
	}
}


#include <iostream>
#include <string.h>
#include <random>
#include <time.h>
#include <string>
#include <sstream>

void TestPF(void func(), size_t count) {
  size_t i = 0;
  clock();
  int time0 = clock();
    while(i < count) {
      func();
      i++;
    };
  int time = clock()-time0;
  printf( "Time=" );
  printf( "%i\n", time );
};

void PFMAlloc() {
  malloc(100);
}

int main() {
  TestPF(PFMAlloc, 10000000);
  return 0;
}


и не надо тут разводить, типа шарп медленнее в этом вопросе
1) Мерьте не со статическим значением, известным в compile-time. Здесь просто C# схлопнул выделение памяти ужемна этапе компиляции, там фактического выделения и не было.
2) Мерьте с помощью std::high_resolution_clock. Здесь вы измерили сишные (не c++) функции clock() и т.п.
3) Мерьте одинаковое: в C++ тоже делайте new int[size].

Здесь, очевидно, ошибка в измерениях. Потому что оптимизирующий режим компилятора должен был выкинуть неиспользуемые вызовы malloc.
И больше не пишите такой жуткий old C-style код.

Я не говорил, что C# тормознее, я говорил, что в скорости выделения памяти все языки должны быть одинаковы, потому что по сути это один системный вызов.
Ничего C# не схлопнул на этапе компиляции, не надо наговаривать!
clock нормально вычисляет.
И там, и там одинаковое количество байт одинаково выделяется!

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

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

Если язык с рантайм-оверхедом побеждает язык без оверхеда, то там происходит какая-то хитрость, например, количество выделяемых байт известно на этапе компиляции, и память выделяется уже при старте приложения ОДИН раз на весь необходимый объём (что и происходит в C#).

Судя по вашему тону и качеству кода даже на C# (про C++ не говорю вообще, то что вы написали — близко не C++), вы ещё студент, и можете не знать, как работает .NET.

То же самое можете сделать и в C++: выделите сразу всю память, а потом делайте placement new.
То же самое можете сделать и в C++: выделите сразу всю память, а потом делайте placement new.

нет, там не вся память выделяется. а кусками.
и это не так просто как ты думаешь. это надо хорошо подумать как сделать.
Вы на самом деле не понимаете, как работает .NET, а ещё пытаетесь защищать. Поизучали бы литературу. Я уж на что не любитель .NET, и то мне очевидно, что раз компилятор знает на этапе компиляции, сколько ему памяти потребуется, то он выделит всю память заранее. Как и в любом языке, где о программистах думают, что они неспособны следить за ресурсами, и поэтому вводят GC.
Чума на оба ваших дома!

Один написал тест, в которcом сравнивает new c malloc и считает, что сравнил С# с C++, другому очевидно, что компилятор C# выделит всю память заранее. Чего ж C++ то заранее память не выделит, а?

В общем. Я не специалист по С++. И по С#. Тем не менее хочу поинтересоваться каким компилятором собирался код для C++? Под какую платформу? Какие флаги оптимизации были использованы?

Под какой виртуальной машиной работает C#? На какой платформе?

И почему для выделения памяти в C# используется new, а в С++ malloc?
> Чего ж C++ то заранее память не выделит, а?

Потому что в C++ такого нет. Компилятор делает такой код, который делает именно то, что ему говорят, и только в нужное время (когда ему говорят).

> И почему для выделения памяти в C# используется new, а в С++ malloc?

По сути это одно и то же, new в любой реализации под основные платформы и компиляторы вызывает malloc (или его аналог).

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

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

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


И так далее касается всего, кроме выделения ресурсов. Т.е. если ты сказал выделить память, и даже если ты потом её не используешь совсем, то компилятору запрещается это вырезать. Если ты сказал открыть сокет, но потом туда ни пишешь, ни читаешь, то компилятору запрещается оптимизировать код так, чтобы сокет не открывался.

Про количество выделямой памяти: компилятор (AOT/JIT) языков с гарбадж-коллектором заранее выделяет нужное (или, если оно неизвестно точно, просто заранее огромным куском) количество памяти с кучи (методами операционной системы) и далее распределяет её эффективнее. Это как раз удобно всяким .NET- и Java-программистам, не желающим следить за памятью.
>если ты сказал выделить память, и даже если ты потом её не используешь совсем, то компилятору запрещается это вырезать. Если ты сказал открыть сокет, но потом туда ни пишешь, ни читаешь, то компилятору запрещается оптимизировать код так, чтобы сокет не открывался.

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


Я тут погуглил и понял, что был не совсем прав. Clang/LLVM (начиная с версии 3.2) и GCC (начиная с версии 5.1) умеют в malloc/calloc/alloca-оптимизации. А вот хвалёный Intel не умеет.

Например, код

#include <string.h>
#include <stdlib.h>

int main(void)
{
  size_t n = 1000;
  int *a = (int*)calloc(n,sizeof(int));
  int *b = (int*)malloc(n*sizeof(int));
  if (a && b)
    memcpy(b,a,sizeof(int)*n);
  return 0;
}


будет соптимизирован (начиная с -O1) в пустышку:

    int main(void)
    {
        return 0;
    }


Асм-код:

main:                                   # @main
        xorl    %eax, %eax
        retq


Поглядеть можно здесь: goo.gl/ILSQvi

(Я сейчас офигел.)
И, получается, топик-стартер вообще был нечестен.

Т.к. функция

void PFMAlloc() {
  malloc(100);
}


компилируется в:

PFMAlloc():                           # @PFMAlloc()
        retq


Я там внизу оставлю ещё коммент.
Это как раз удобно всяким .NET- и Java-программистам, не желающим следить за памятью.

Вот это сильно. С потоком, например, в 4 гигабита внутри программы за памятью надо следить в любом случае. И для меня на .NET это просто быстрее реализовать.
А что за программа на .NET с потоком 4 гигабита внутри программы?
Моя программа, десктопная. У серверного .NET для высоких нагрузок другие режимы сборки мусора доступны. Да и на Java куча highload софта написана.
Кто же new c malloc сравнивает. Или используйте unsafe или добавляйте critical section. malloc синхронизирует каждый вызов к куче, new в C# этого не нужно.
Да тоже нет, new это емнип malloc + конструктор. У C# память выделяется огромными блоками, поэтому вызовов malloc вообще минимальное количество, доступ к куче не локается и куча тредов может параллельно объекты создавать, т.к. память системой давно уже выделена. Для C++ есть же готовые решения для пакетного malloc, вот их и надо сравнивать с C#. Хотя сам идея сравнивать «высокоуровневый ассемблер» с «рантайм песочницей» — бред.
Скомпилируйте C++ с оптимизацией и вы немного удивитесь:

vladon@vldn-dev:~/projects/temp$ clang++ -O3 -std=c++11 test.cpp -o test
vladon@vldn-dev:~/projects/temp$ ./test
Time=0


А я-то не стал за вами проверять. Эх я, пепел на мою голову.
Кстати, неплохой вариант попасть на хабр. Пишем подобную статью, получаем приглашение от НЛО, обнуляем карму, ждем пару недель. На выходе нулёвый акк. Profit же.
Есть менее трудозатратные способы. Например, написать о чем-нибудь интересном…
Интересно, а на гиктайм, и мегамозг как попадают.
Если вы хотите стать приверженцем развращающей демократии C++, то туда вам и дорога, вы вправе сделать этот выбор. Но если вы готовы постичь нирвану в программировании, сохранить свои нервы, здоровье и здравомыслие, то ваш выбор ‒ C#.

А если, я хочу скорость? Не раскрыт вопрос того как добиться высокой скорости на C#, или про это будет отдельная статья?
Sign up to leave a comment.

Articles