Drive your career as React Developer with Symphony Solutions!
×Закрыть

Недружелюбность C++ к новичкам: взгляд Unity-разработчика

Привет, меня зовут Максим. Я программист-самоучка, свою первую строчку кода написал еще в 1994 году и на текущий момент принял участие где-то в 10 игровых проектах.

За это время мне пришлось писать на множестве различных языков:

  • с 8 по 11 класс самостоятельно изучал BASIC и Turbo Pascal в компьютерном классе школы;
  • писал игры для калькулятора МК-61 дома (в средине 90-х компьютер был роскошью);
  • Delphi на первой работе;
  • Lua и немного C++/CLI на второй, с которой я вошел в GameDev 14 лет назад;
  • Python и C++ под Bigworld на третьей;
  • C# под Unity на текущей;
  • в свободное время делаю свои проекты;
  • та же некоторое время посвятил изучению Rust и D в попытке найти альтернативу C++.

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

В очередной раз с C++ работал пару месяцев назад, когда взялся разбираться с Unreal Engine. И поразился, сколько еще неизведанного осталось для меня в этом языке. Сколько еще подводных камней и возможностей выстрелить себе в ногу я подзабыл или не нашел при прошлых опытах использования.

Эта статья — попытка осмыслить, почему у C++ такой высокий порог вхождения, чем он уступает другим языкам. А также почему я считаю его плохим языком для программистов-новичков.

Целевая аудитория:

  • те, кто считает, что начинать нужно со сложного и максимально эффективного;
  • те, кто много писал на C#|Java и подобных и хочет покорить новые вершины;
  • те, кто имеет некоторый опыт C++, но, как и я, понимает, что нет предела совершенству;
  • практикующие системные программисты на языках типа Rust и D, которые смогут рассказать, почему выбрали их альтернативой C++;
  • гуру C++, которые в комментариях просветят неопытных, чем и на сколько оправданы те или иные подходы языка, затронутые в статье.

Иллюстрация Ульяны Патоки

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

Компиляция и линковка

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

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

Чтобы не включать избыточую для компилятора информацию (ему код включаемых функций не нужен), договорились все заголовки функций выносить в отдельные файлы, которые и назвали заголовочными. Таким образом возникло разделение, что заголовочные файлы имеют расширение .h (иногда пишут .hpp, чтобы явно указать, что это написано на С++), а файлы с кодом — в файлах с расширением .cpp.

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

Приведу пример.

Сейчас я работаю над проектом на C# под Unity, который состоит из 4300 файлов с кодом. Это 25 мегабайт исходников! Время полной компиляции проекта на моем компьютере занимает 10 секунд. Берем пустой проект на Unreal, добавляем единственный объект с С++ классом. Вносим туда малейшее изменение — время компиляций и линковки до запуска, минимум 7 секунд на том же i7-8700.

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

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

Для компилятора это может быть проблемой, и для ее решения сейчас используют директиву препроцессора #pragma once. Выглядит это так:

// unit.h
#pragma once
 
void f1(); // пример описания заголовка (сигнатуры) функции f1()
bool f2(int x); //пример описания заголовка (сигнатуры) функции f2()

Но вы можете увидеть и такой вариант решения проблемы, которым пользовались до появления поддержки #pragma once:

// unit.h
#ifndef __UNIT_H__
#define __UNIT_H__
 
void f1(); // пример описания заголовка (сигнатуры) функции f1()
bool f2(int x); //пример описания заголовка (сигнатуры) функции f2()
 
#endif

Здесь для каждого файла программист придумывал уникальное имя константы препроцессора. Тогда код между #define и #endif включался только при первой попытке подключить этот заголовочный файл к текущему компилируемому файлу.

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

2. Второй прикольчик связан с правилом компилятора «все, что выглядит как декларация функции, будет считаться декларацией функции».

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

ClassA obj1(10); //создание объекта путем вызова конструктора с одним параметром
ClassB obj2(true, 2); //создание объекта путем вызова конструктора с двумя параметрами
ClassC obj3(); //объявление функции без параметров, которая возвращает значение типа ClassC
ClassC obj4; //создание объекта, путем вызова конструктора без параметров

Но проблемы с компилятором легко могут возникнуть и при использовании конструктора с большим числом параметров. Для приведения значения к другому типу С++ представляет два похожих варианта (T)value и T(value). Правда, это работает не всегда. Вопрос к новичкам: как думаете, где вызов конструктора, а где декларация заголовка функции?

int k = 5;
ClassA obj1((float)k); 
ClassA obj2(float(k));

В данном случае компилятор посчитает, что obj1 — создание объекта путем вызова конструктора с одним параметром, а obj2 — это объявление функции, которая в качестве параметра получает значение типа float. Как видите, для того чтобы вас поняли правильно, нужно знать немало тонкостей.

3. Добавление новых функций, а также изменение сигнатуры существующих.

Заголовочные файлы приходится создавать почти для каждого класса, и чисто технически сделать это не сложно. Тем более, что в IDE этот процесс автоматизирован.

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

Простой пример:

// file main.cpp
#include"A.h"
 
int main()
{
    A a;
    a.f();//если закомментировать единственное обращение к «забытому» методу, ошибок не будет
    return 0;
}
// file A.h
#pragma once
 
class A
{
public:
    A();
    ~A();
    int f();//эту функцию «забыли» добавить в .cpp файл
};
// file A.cpp
#include "A.h"
 
A::A()
{
}
A::~A()
{
}
 
Получим такое сообщение об ошибке 
main.obj : error LNK2019: unresolved external symbol "public: int __thiscall A::f(void)" (?f@A@@QAEHXZ) referenced in function _main

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

4. Преобразование обычного класса в шаблонный.

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

В C# такая трансформация проводится достаточно просто. Например, такой примитивный класс:

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

Преобразование выразилось в замену стартового типа шаблонным и приписыванием <T> после имени класса.

Теперь посмотрим, что нужно сделать в случае C++. Было:

Для преобразования недостаточно только заменить int типом T. Придется еще много чего дописать (см. выделение цветом), прежде чем компилятор скомпилирует этот класс:

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

Но при желании можно все свести к еще более компактному виду. Ничего не напоминает?

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

5. Сборка проекта — линковка.

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

Вот как выглядит очередная итерация после внесения изменений в проект:

  • перекомпилируем те файлы, которые изменились;
  • запускаем специальную программу-линковщик. Ей на вход нужно передать путь к абсолютно всем объектным файлам, из которых состоит проект;
  • переходим к следующему шагу, если линковщик не выявит недостачи кода функций, которые были обещаны компилятору во всех заголовочных файлах. Если же линковщик будет недоволен — ждите «информативных» сообщений об ошибках;
  • результатом работы этой программы будет либо исполняемый файл, либо библиотека — в зависимости от ключей запуска.

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

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

А как с компиляцией в других языках

Современные языки пошли другим путем, так как появились позже, и их разработчики могли оценить сложности подхода С++ в отношении компиляции проектов. Обычно единицей компиляции служит условный модуль. Компилятор заранее знает, из каких файлов состоит то, что он компилирует, и сам может построить все связи. При чем этот подход исповедуют не только прикладные языки типа C# или Java, но и вполне системные Rust и D.

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

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

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

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

Современные подходы в языках, которых не встретишь в С++

Наследование

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

Оставим обсуждение, на сколько это канонично, теоретикам, а на сколько практично — гуру. Лично мне не нравится, что указание типа наследования не сделали обязательным и в то же время по умолчанию разным для struct и class — public и private соответственно.

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

class A
{
};
 
class AA: A
{
};
 
int main()
{
    A* bp = new AA;//Error: E0269	conversion to inaccessible base class "A" is not allowed
    return 0;
}

Лечится просто, наследование следовало описать так: class AA: public A, но судя по stackoverflow.com/...​ritance-inaccessible-base, очевидным такое сообщение компилятора оказывается не для всех.

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

Модификаторы доступа

В С++ используется блочный подход:

private:
//все что до следующего модификатора приватно
    float _someValue;
    bool _someFlag;
    string _someName;
 
public:
//все что до следующего модификатора публично
    float GetSomeValue() { return _someValue; }
    bool GetSomeFlag() { return _someFlag; }
    string GetSomeName() { return _someName; }
protected:
    string SetSomeName(string name) { _someName = name; }

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

Кроме того, в C# мне нравится располагать рядом приватные данные и публичные методы доступа к этим данным, чему блочный подход никак не способствует:

    private float _someValue;
    public float SomeValue { get { return _someValue; } }
 
    private bool _someFlag;
    public bool SomeFlag { get { return _someFlag; } }

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

    public string SomeName { get; private set; }

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

Переопределение методов

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

До стандарта С++11 у разработчиков не было даже возможности указать на то, что метод переопределяется. Эта задача полностью лежала на компиляторе. Сейчас в стандарт добавлено ключевое слово override, которое можно встретить и в других языках, но в C++ оно, в угоду обратной совместимости, полагаю, не есть обязательным.

Есть несколько случаев, когда явное использование этого override окупается:

  1. Компилятор перепроверит, а вы точно переопределяете то, что хотели:
    • Когда вы только переопределяете метод в классе-наследнике, то можете ошибиться в сигнатуре (названии, количестве или типе параметров).
    • Когда в какой-то момент вы решите удалить виртуальный метод из базового класса, то компилятор укажет на все переопределенные методы в классах-наследниках. И пока вы не решите эту проблему — проект не скомпилируется.
    • В языках, где переопределение обязано быть явным, компилятор предупредит и о ситуации, когда в классе-наследнике был определен метод, а через некоторое время кто-то пробует в базовом классе добавить метод с такой же сигнатурой.
  2. Виртуальные деструкторы. Эта проблема оказалась настолько массовой, что ее упоминают чуть ли не в каждой книге. Но это не мешает новичкам постоянно наступать на эти грабли. Потому упомяну о ней и я немного.

Если, начиная со стандарта С++11, вы приучитесь дописывать override в деструкторах классов-наследников, компилятор вас предупредит, если окажется, что в базовом классе деструктор не был объявлен виртуальным.

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

//A.h
#pragma once
#include<string>
using std::string;
class A
{
protected:
    string _name;
public:
    A() : _name("class A. ") {}
    ~A();
};
 
//A.cpp
#include"A.h"
#include<iostream>
 
using std::cout;
using std::endl;
 
A::~A()
{
    cout << _name << "~A()" << endl;
}
//AA.h
#pragma once
#include "A.h"
class AA: public A
{
public:
    AA() { _name = "class AA. "; }
    ~AA();
};
//AA.cpp
#include "AA.h"
#include <iostream>
using std::cout;
using std::endl;
 
AA::~AA()
{
    cout << _name << "~AA()" << endl;
}
//main.cpp
#include<iostream>
#include "AA.h"
 
using std::cout;
using std::endl;
 
int main()
{
    {
        A aa = AA();
    }
 
    A* ap = new AA;
    cout << "\nTest destroy ap" << endl;
    delete ap;
 
    return 0;
}

Результатом работы такого небольшого приложения будет вывод:

class AA. ~AA()
class AA. ~A()

Test destroy ap
class AA. ~A()

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

Понимаю, что проблема обратной совместимости не дает возможности сделать использование override обязательным для всех виртуальных методов, но посильной задачей для компилятора было бы отлавливать все ситуации наследование классов от базовых с невиртуальными деструкторами и выводить сообщение об ошибке. Тем не менее, Visual Studio 2017 даже предупреждений не выдает при компиляции выше приведенного примера ☹. И пока требование на такое поведение компилятора не включат в стандарт, С++ разработчики будут продолжать обжигаться об невиртуальные деструкторы.

Перегрузка методов

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

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

Предположим, есть такой базовый класс B:

//B.h
#pragma once
#include<string>
using std::string;
class B
{
protected:
    string _name;
public:
    B() : _name("class B. ") {}
    virtual void F();
    virtual void F(int v);
    virtual void F(bool v);
    virtual ~B(){}
};
//B.cpp
#include"B.h"
#include<iostream>
 
using std::cout;
using std::endl;
 
void B::F()
{
    cout << _name << "B.F()" << endl;
}
void B::F(int v)
{
    cout << _name << "B.F(int " << v << ")" << endl;
}
void B::F(bool v)
{
    cout << _name << "B.F(bool " << v << ")" << endl;
}

Со временем нам понадобился класс-наследник:

//BB.h
#pragma once
#include "B.h"
class BB: public B
{
public:
    BB() { _name = "class BB. "; }
    ~BB() {}
};

Любой, кто уже имел опыт работы с современными ООП языками, не будет удивлен, что у объектов класса BB есть доступ ко всем методам, определенным только в классе B. И им не обязательно быть виртуальными:

//main.cpp
#include<iostream>
#include "BB.h"
 
using std::cout;
using std::endl;
 
int main()
{
    BB bb;
    bb.F(100000);//test 1
    bb.F();//test 2
 
    system("pause");
    return 0;
}

Но стоит только в классе BB переопределить или перегрузить любой метод F():

//BB.h
#pragma once
#include "B.h"
#include <string>
#include <iostream>
 
using std::cout;
using std::endl;
 
class BB: public B
{
public:
    BB() { _name = "class BB. "; }
    void F() override {}                            //1
    void F(string s) {}                             //2
    void F(short a) { cout << "BB:F(short " << a << ")" << endl; } 	//3
    ~BB() {}
};

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

Предлагаю провести эксперименты:

  1. Оставить функцию 1 и закомментировать 2 и 3.
    Не скомпилируется test1.
  2. Оставить функцию 2 и закомментировать 1 и 3.
    Не скомпилируется test1 и test2.
  3. Оставить функцию 3 и закомментировать 1 и 2.
    Не скомпилируется test2. А вот test1 скомпилируется, но на экран будет выведено не B::F(int 100000), а BB::F(short −31072).

Чтобы в классе-наследнике BB стали доступны спрятанные таким образом методы F базового класса B, нужно указывать using B::F; внутри класса BB.

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

Неявный вызов конструктора

Еще один спорный, на мой взгляд, момент — возможность языка С++ задавать конструкторы как явные/неявные. И ладно бы значением по умолчанию выбрали явное поведение, нет — все конструкторы без explicit в сигнатуре позволяют неявное преобразование. До стандарта С++11 такими были только конструкторы с одним параметром, после — такими стали все, где можно применять list initialization.

struct A
{
    A() { }           
    A(float) { }    
    A(int, int) { }
};

Если есть такое описание класса A, то можно создавать объекты этого класса достаточно непривычным образом:

int main()
{
    A a1 = 11;      
    A a2({});   
    A a3{0, 1};    
    A a4 = {5, 10}; 
    A a5 = (A)20;   
} 

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

void f(A value)
{
 
}
int main()
{
    f({});
    f(10);
}

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

Наверное, поэтому маститые авторы рекомендуют всегда объявлять конструкторы как explicit. Очень странно, что в стандарте этот вопрос так и не был исправлен.

Инициализация переменных

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

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

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

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

struct A
{
public:
    int Y;
    int X;
    int Z; //забытые переменные остаются на вашей совести
 
    A(int value) :
        X(value + 1),
        Y(2*X) //в списках инициализации опасно полагаться на значения других полей
    {
        //классический для других языков способ инициализации в С++ считается не эффективным
        //X = value + 1;
        //Y = 2*X;
        //Z = 0;
    }
};

К сожалению, никто не подскажет, если после вызова конструктора у вас все же останутся неинициализированные поля.

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

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

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

Кроме того, компиляторы таких языков позаботятся, чтобы поля всех классов были проинициализированы дефолтными значениями, либо сообщат, что вы забыли каким-то полям присвоить значение.

Компилятор и совместимость

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

Даже стандарт разные компиляторы поддерживают избирательно en.cppreference.com/w/cpp/compiler_support.

Как можно увидеть по ссылке выше, есть несколько версий стандарта языка С++:

  • Первые упоминания о нем относятся к 1983 году, а первая коммерческая версия появилась в 1985-м.
  • В 1998 году вышла первая версия стандарта С++98.
  • В 2003-м С++98 был немного доработан и появился стандарт С++03.
  • В 2011-м был принят стандарт С++11, который вносил значительный объем изменений.
  • В 2014 году С++11 был немного доработан и вышел С++14.
  • Язык развивался, и в конце 2017-го выпустили стандарт С++17. Он добавил кое-что новое и полезное в язык, но изменения по масштабу уступают привнесенным при выходе С++11.
  • В мае 2020 года ожидается утверждение стандарта С++20, который по объему изменений должен потягаться с С++11.

И даже сейчас можно найти возможности С++11 или С++14, которые не поддерживаются или не до конца реализованы в тех или иных компиляторах.

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

Информативность компилятора

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

Если же усвоить простое правило, что не важно, сколько строк ошибок вывел компилятор, искать проблему нужно в самой первой, то привыкнуть можно. Хотя сообщения об ошибках в template-классах иногда ошарашивают многословностью.

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

Компиляция шаблонных классов

Шаблонный класс — это болванка класса, в которую будет подставлен конкретный тип/типы в момент создания объекта этого класса.

До этого компилятору особо нечего проверять. Предполагается, что в момент инстанцирования объекта он получит всю необходимую информацию и тогда проверит, допустим ли такой тип для этого класса. Если нет, то такой код не скомпилируется.

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

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

К счастью, в Visual Studio, начиная с версии 2017 15.8, появился Template Bar, который позволяет указать конкретный тип для шаблона. И после компилятор проверит, скомпилируется ли этот класс с этим типом, причем с учетом всех методов, которые есть в классе.

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

Как обычно, описанные проблемы с шаблонными классами неведомы разработчикам на других языках. Да, по результирующей мощности шаблонов мало какой язык может тягаться с С++ (D может 😉). Но удобство их использования куда важнее, преимущественно за счет возможности накладывать ограничения на шаблонные параметры. Это позволяет компилятору и IDE определять, что делает этот тип, и показывать ошибки сразу после того, как они были допущены, даже без необходимости что-то компилировать.

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

Четкие правила — залог успеха

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

Разберем простой пример. У нас есть такой вызов функции:

f(shared_ptr<SomeObj>(new SomeObj), f2());

Опытные разработчики скажут, что здесь лучше код переписать так:

auto p = shared_ptr<SomeObj>(new SomeObj);
f(p, f2());

Еще более опытные скажут, что здесь напрашивается использование std::make_shared, доступное с C++11, как более безопасное и эффективное.

auto p = std::make_shared<SomeObj>();
f(p, f2());

Но далеко не все скажут, чем именно опасен первый вариант.

А опасен он потенциальной утечкой памяти.

Причина этой потенциальности в том, что в первом варианте, кроме вызова функции f(), компилятор видит еще три действия:

  1. new SomeObj — создание объекта типа SomeObj;
  2. передача созданного объекта в конструктор shared_ptr;
  3. вызов функции f2().

И стандарт позволяет компилятору скомпилировать эти вызовы в порядке (1, 3, 2), если по какой-либо причине ему так захочется. Следовательно, если при вызове метода f2 будет сгенерировано исключение, то не вызовется не только метод f(), но и конструктор shared_ptr<SomeObj>. Тогда не будет уничтожен объект, созданный в пункте 1. Вот и утечка памяти.

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

Классы с похожим поведением, но разным интерфейсом

Напоследок хочу привести простой пример, который, думаю, известен всем C++ разработчикам, начиная от уровня Middle. Это проектная ошибка, которая до сих пор заставляет спотыкаться новичков, казалось бы, на ровном месте.

Реализация класса vector<bool> отличается от реализации для всех других типов тем, что здесь флаги хранятся в упакованном виде. Это позволяет в одном байте хранить до 8 флагов. И приводит к тому, что operator[] возвращает T& для всех типов аргумента, кроме bool, для которого возвращается тип vector<bool>::reference.

Если предположить, что у вас есть функция vector GenerateRandomFlags(), то будет абсолютно безопасно писать так:

 bool flag = GenerateRandomFlags()[0];
    cout << flag;

Поскольку vector::reference умеет неявно преобразовываться в тип bool. Но все становится печально, если положиться на оператор auto:

    auto flag = GenerateRandomFlags()[0];
    cout << flag;

Программа упадет при попытке обратится к значению переменной flag.

В одной из своих книг Мейерс посвящает две страницы объяснению рекомендации не использовать auto для прокси-классов. И еще две страницы рассказывает, как понять, что есть прокси-классом. Я же всего лишь рекомендую: каждый раз, когда вам придется для некоего типа делать специализацию шаблона, требующую изменения интерфейса — пожалейте себя и других пользователей этого класса, дайте ему другое имя!

Ну а если вам понадобиться коллекция флагов в классическом виде, то придется выкручиваться, используя vector<char>.

Вывод

У опытных C++ разработчиков давно могла возникнуть мысль: «Не нравится язык — не используй! Он для суровых парней, которые привыкли к трудностям и могут в голове удержать тысячу нюансов. Накрепко выучили множество правил и всегда используют проверенные методики, что позволяют обходить проблемные места».

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

В данный момент мне чрезмерно комфортно быть C# разработчиком под Unity и хочется новых вызовов в лице C++ с Unreal Engine. Поскольку хороших альтернатив на других высокопроизводительных языках я не знаю, буду признателен тем, кто поделиться со мной и другими читателями ссылками на материалы, которые научат правильному использованию современного C++, особенно в контексте Unreal. А также ускорят попадание в клуб избранных, куда добираются единицы. Заранее спасибо.

Буду рад, если мои мысли окажутся полезными для целевой аудитории. В случае позитивного отклика обещаю еще поделиться личным опытом решения достаточно простой задачи, которую на одних онлайн-курсах за 4 года пыталось решить более 11 000 человек, а осилили только 500. Код ее решения я написал быстро, но вот на борьбу с компилятором ушло намного больше времени.

LinkedIn

Лучшие комментарии пропустить

C++ очень дружелюбный язык. Только друзей он себе выбирает очень тщательно.

Чекаю на статтю «Почему язык ассемблера такой недружелюбный к программистам»
Бо дійсно, що це за два альтернативних синтаксиса — Intel і AT&T? Де стандарт? Що це за розширення на розширенні, я не встиг вивчити x87 а тут MMX, тільки взявся — одна за одною нові версії SSE. Я тільки до SSE2 довчив а вже час вчити AVX, і тих вже кілька.
ООП не підтримується, метапрограмування немає, є тільки архаїчні макроси. І взагалі, хіба автори асемблера не чули про сучасни тренди у мовах програмування — ось в Rust є ownership rules, а в Хаскелі чистота. Як написати на асемблері чисту функцію? І чому забули про «скомпілилося — працює»?

Хорошая попытка, но нет. )

Серьезно. У С++ есть достаточно много криво реализованных, переусложненных или недоделанных фич, овиринжиниринга на ровном месте и тд.
Сесть и быстро переделать все «по-человечески» нельзя, потому что это сломает обратную совместимость с существующим кодом, и в результате получится новый язык, который будет никому не нужен. Александреску вон запилил свой D, и где он теперь?

Потому комитет делает то, что может: вводит новые фичи на замену совсем уж сумрачным легаси-костылям; потихоньку депрекейтит/выпиливает из стандарта то, что можно убрать без радикального ломания всего языка и тд.
С другой стороны ему помогают производители компиляторов: улучшают диагностику ошибок; изобретают новые инструменты анализа кода, которые позволяют свести к минимуму возможности для «стрельбы в ногу» и тд.

Idiomatic C++ на сколь-либо современных проектах в 2020 году кардинально отличается от того, что было 20 и даже 10 лет назад. Рядовой гребец в прикладном коде достаточно редко сталкивается с темными сторонами языка (при правильно налаженных процессах, конечно). Берем из интернета набор guidelines, прописываем к себе в кодстайл, форсим проверку стиля для всех коммитов в CI (clang-tidy/sanitizers/whatever), и, внезапно, абсолютное большинство «стандартных» проблем отсеивается автоматически. В итоге ситуация изнутри выглядит далеко не так страшно, как кажется сторонним наблюдателям после чтения таких вот статей.

Проблема этой статьи (и большинства подобных ей) в том, что вместо действительно серьезных недостатков языка в ней в ассортименте представлены:

  • Entry-level штуки, которые через пару месяцев использования языка перестаешь замечать и делаешь на автомате, вроде необходимости писать #pragma once в хедерах или указания public при наследовании классов;
  • Истории про то, как можно подергать язык за устаревшие костыли, для которых давно сделаны более «правильные» аналоги, и посмотреть, как он эпично валится при этом (вроде «прикольчиков» из секции про круглые скобки и классы);
  • Легкие передергивания, добавленные для нагнетания полемического угара («посмотрите, как они в консоли каждый раз перечисляют все 100 файлов, которые нужно слинковать, ну ващщщеееее!», или «у вас вот тут ошибка неочевидная, новичку без гугла непонятно»);
  • Просто questionable утверждения, к которым для солидности дописан пассаж про «современный подход одобряет такое», чтобы читатель испугался и не усомнился. )
  • Если воспринимать написанное с точки зрения «я несколько месяцев покодил на С++, и вот что я думаю» — получилась добротно сделанная (хоть и достаточно шаблонная) статья. Но делать каких-то глобальных выводов о языке на ее основе не стоит. Немного комментариев/уточнений ниже:
    Компиляция и линковка
    Не буду комментировать подробно, потому что модули решают бОльшую часть описанных проблем. Да, даже #pragma once писать не нужно. )
    Сейчас я работаю над проектом на C# под Unity, который состоит из 4300 файлов с кодом. Это 25 мегабайт исходников! Время полной компиляции проекта на моем компьютере занимает 10 секунд.
    Сорян, но не верю. C#, конечно, по очевидным причинам собирается заметно быстрее, но не прям настолько. Вероятно это была не совсем полная компиляция. Так-то я тоже могу включить ccache и вворачивать для красного словца, как у меня гигантские проекты пересобираются с нуля за секунды. )
    2. Второй прикольчик связан с правилом компилятора «все, что выглядит как декларация функции, будет считаться декларацией функции».
    Just use uniform initialization syntax, Luke.ClassA obj1{10}; ClassB obj2{true, 2}; ClassC obj3{};Его запилили в том числе и для избавления от всех описанных в этом пункте «прикольчиков».
    Получим такое сообщение об ошибке
    main.obj : error LNK2019: unresolved external symbol «public: int __thiscall A::f(void)» (?f@A@@QAEHXZ) referenced in function _main
    А что не так? «Ты вызываешь такой-то метод из функции мейн, но я не нашел его определение», как тут можно расписать еще подробнее?Более интересная ситуация получается, если сделать все виртуальные методы класса инлайновыми. Тогда линкер может пожаловаться на ненайденную vtable, и тут действительно начинающему может быть не совсем очевидно, чего от него хотят. Придется разок загуглить ошибку и запомнить причину со стековерфлоу.
    Клик по ошибке в IDE не подсвечивает строчку
    Действительно, как страшно жить ) Линкер не может показать тут номер строки, потому что у него в общем случае нет доступа к исходникам. IDE технически может ткнуть пальцем в объявление метода в хедере; почему она этого не делает — это уж точно не проблема языка.
    Эта необходимость держать два файла синхронизированными заставляет постоянно копипастить куски кода, что в мире программирования, как правило, порицается.
    Немного странная претензия: разделение интерфейса и реализации придумано задолго до С++, и используется сейчас практически во всех статически типизированных языках. В этом вашем С# вон точно так же нужно «копипастить» сигнатуру метода из интерфейса, и что?К тому же все строго добровольно: хочешь спрятать реализацию класса, чтобы не нужно было ребилдить всех причастных при ее изменении — «копипастишь»; не хочешь копипастить, и готов мириться с ребилдами — кладешь все в хедер; боишься за время компиляции — см. модули.
    Правда, громоздкий вариант выноса определения функций за пределы шаблонного класса тоже часто встречается. Уж не знаю, какие преимущества он дает.
    Он позволяет мне быстро просмотреть интерфейс класса и не отвлекаться на пролистывание реализации его методов, например. А еще реализацию можно вынести из заголовка в отдельный файл, и инклудить его только тогда, когда это действительно нужно.
    Если вы собираете проект через командную строку, как показывают во многих книгах или на онлайн-курсах, то без ухищрений процесс сборки сложно назвать увлекательным.
    Если вы набираете текст на клавиатуре одним пальцем, то процесс получается тоже не очень увлекательным. Потому вы так не делаете, верно? )В книгах показывают, как собирать хелловорлды из командной строки, чтобы дать читателю хотя бы базовое понимание того, что происходит во время компиляции. Чтобы он не впадал в прострацию при виде сообщения про unresolved external от линкера, например. ) Никто не учит собирать так реальные проекты.
    программисты создают так называемые makefiles, где заранее определяют, из чего состоит проект и как его собирать. Работа с ними требует определенных знаний.
    В простейшем случае не требует, достаточно скопипастить две строчки из той самой книжки, которая учила собирать хелловорлд. Более сложные случаи будут разруливаться либо в IDE, либо в хайлевельных системах сборки типа CMake. Которые уже таки да, «требуют определенных знаний», как и msbuild для C#, например )
    Сейчас наличием IDE для языка никого не удивишь, но С/С++ программисты долго жили без этих благ цивилизации
    Когда это они успели? ) Первые коммерческие компиляторы С++ эволюционировали из компиляторов C, которые на тот момент уже поставлялись вместе с зародышами IDE (см. Turbo C). По крайней мере описанную выше проблему с необходимостью ручного добавления новых файлов в проект они точно умели решать.
    Кроме того, в C# мне нравится располагать рядом приватные данные и публичные методы доступа к этим данным
    Явная же вкусовщина, ну. Мне нравится наоборот, но это ж не повод делать далеко идущие выводы о языках/современных подходах.Да и никто не мешает писать на С++, как на С#, если так уж хочется (а верно ли обратное, кстати?):public: float GetSomeValue() { return _someValue; }private: float _someValue;public: еще один геттер;
    Модификатор доступа как часть заголовка каждого метода и поля класса дает большую гибкость в организации кода и повышает его читаемость. Думаю, поэтому в современных языках такой подход — распространенная практика.
    Пассаж про «большую гибкость» выглядит достаточно иронично в свете предыдущего абзаца, не правда ли? )
    в стандарт добавлено ключевое слово override, которое можно встретить и в других языках, но в C++ оно, в угоду обратной совместимости, полагаю, не есть обязательным.
    Верно. Потому все современные компиляторы умеют выдавать ворнинги при его отсутствии; эти ворнинги можно прокачать до ошибок и сделать override обязательным у себя на проекте.
    но посильной задачей для компилятора было бы отлавливать все ситуации наследование классов от базовых с невиртуальными деструкторами и выводить сообщение об ошибке.
    Они, в общем, так и делают. Проблема приведенного фрагмента кода в том, что в таком виде он далеко не всегда является ошибкой. Я могу сделать иерархию классов, и передавать по коду указатель на наследника в виде base* туда, где нужны только данные родителя. Если у меня в базовом классе нет виртуальных методов, и я гарантирую, что delete всегда вызывается с правильным типом, я могу не делать деструктор виртуальным, и таким образом избежать появления vtable у рассматриваемых классов. Это, конечно, достаточно небезопасное решение, к которому приходится прибегать только в исключительных случаях, но компилятору приходится его учитывать в любом случае. ВИначе пришлось бы выдавать ворнинги на каждое обращение к такому наследнику через указатель на базу, чего делать нельзя из тех же соображений обратной совместимости.TL;DR: если в приведенном примере добавить хотя бы один виртуальный метод в базовый класс — компилятор поймет, что это почти точно ошибка, и выдаст ворнинг (гцц/шланг точно, студии под рукой нет, чтобы проверить).
    Перегрузка методов
    В С++11 хотели запилить возможность запрещать «прятать» методы родителя (это включалось бы атрибутом у класса, чтобы не ломать обратную совместимость). Но пропозал не прошел, потому нужно смотреть на ворнинги.
    Неявный вызов конструктора
    Тут соглашусь, я бы тоже сделал наоборот (explicit by default, implicit on demand). Но, опять же, обратная совместимость.
    В С++ из языка С перекочевала особенность не инициализировать локальные переменные значениями по умолчанию.
    В 2020 ворнинги и санитайзеры решили эту проблему. К тому же в шланге вон запилили расширение, которое позволяет автоматически инициализировать все локальные переменные нулем. Боюсь, скоро начитаются жалоб и включат его по умолчанию (
    С полями классов ситуация еще хуже. Они могут быть автоматически инициализированными дефолтными значениями, а могут и не быть — зависит от ситуации, в какой создается объект. Правила инициализации достаточно сложные, чтобы их записать в короткой статье.
    Правила инициализации достаточно простые: если у вас в классе есть поля фундаментальных типов (int, bool, «чистый» указатель и тд) — их всегда нужно инициализировать вручную. Это все, что вам нужно знать в 99.99% случаев.
    язык провоцирует использовать списки инициализации для всех полей.
    Ну не знаааю, лично меня язык провоцирует инициализировать большинство полей прямо в определении класса (int field = 123;). Списки инициализации нужны для вызова конструкторов базовых классов, и для полей, у которых инициализация нетривиальная и ее не хочется класть в хедер.
    К сожалению, никто не подскажет, если после вызова конструктора у вас все же останутся неинициализированные поля.
    Как и с локальными переменными выше: в тривиальных случаях компилятор выдаст ворнинг при обращении к такому полю; в нетривиальных сагрится санитайзер.
    Поэтому либо никогда не используйте значение полей как базовое для других полей в списке инициализации, либо никогда не меняйте порядок их объявления в теле класса!
    Либо читайте, что вам говорит компилятор: «warning: field2 will be initialized after field1»
    И мне не понятно, зачем были придуманы такие сложности, которых в других языках нет.
    Потому что во время, когда придумывались эти сложности, других языков еще в проекте не было ) (см. выше про обратную совместимость)
    Например, не гарантируется, что порядок вычисления значений параметров при вызове метода будет строгим.
    Есть такое. В общем случае наиболее эффективно считать параметры в том порядке, в котором они должны попасть в стек; этот порядок зависит от calling convention; соответственно если его строго задать в языке, компилятору придется генерить чуть менее эффективный код. 30 лет назад это было достаточным аргументом. Сейчас уже пара лишних инструкций погоды не сделают, потому в С++17 для многих случаев порядок наконец задали; думаю, в одном из следующих стандартов зададут и для всего остального. В остальном проблем с совместимостью между современными версиями компиляторов в 2020 году практически не осталось. Они есть, но в рядовом прикладном коде вы с ними не столкнетесь.
    На личном опыте неоднократно сталкивался с тем, что достаточно простой код, который запускается и корректно работает на MSVC, даже не компилируется при использовании gcc и наоборот.
    Тут я вынужден реквестировать пруфы с примерами достаточно простого кода, который корректен с точки зрения языка, и при этом так себя ведет на современных версиях упомянутых компиляторов. Только из личного опыта, а не из багрепортов в интернете, пожалуйста. :)
    Четкие правила — залог успеха
    Приведенный там пример с shared_ptr больше не актуален: начиная с С++17 параметры вычисляются всегда строго последовательно (хоть и в неопределенном порядке), см. линк на PDFку выше.

Не сказал бы, что С++ такой уж прям сложный и недружелюбный. Если в правильном порядке всё изучать, то окажется уже не так уж и сложно. А то у многих фобия к С++ начинается с универов. Где учат в стиле:
Ой, вы не знаете как работает память, что такое хип и стэк? Ну держите хренову тучу задач на указатели
Или же
А сделайте вот эту задачу, но STL юзать нельзя, мы же еще не проходили темплейты!1

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

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

188 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Очень длинная статья

Спустя неделю, посмотрев на статью с учетом всех комментарией, должен признать, что попытка замахнуться на глобальное сравнение с другим языками, мягко говоря, удалась не очень. Но тем не менее, вполне получилось показать некоторые примеры проблем, с которыми так или иначе сталкиваются новички и к которым давно привыкли старички :).
Так же сделал для себя вывод, что С++ изменяется со временем достаточно сильно. Так часть примеров была взята из последнего 3го издания достаточно известной книги Effective C++ Мэйерса, но которая вышла в 2005 году. За это время достаточно много чего изменилось и многое уже стало не актуально, особенно если использовать более новые возможности языка.
Здесь напрашивается второй вывод: «хотите писать на С++ правильно — ищите как решать возникшую проблему современными средствами».
Уже после написания статьи, с целью лучше разобраться, что нас ждет в с++20 нашел книгу changkun.de/...​rn-cpp-tutorial-en-us.pdf , которая может служить неплохим примером того, какие проблемы решали разработчики языка и как у них это получилось.
Еще, для тех, кто хочет пощупать новые возможности с++20 уже сегодня, рискну порекомендовать цикл видео этого автора www.youtube.com/...​eBDH6i4Rr3E2ZYLUq6_Rdq3Dr
У него специфический английский, но следуя его инструкциям я смог настроить у себя последние версии 3 различных компиляторов и попробовать в действии те возможности языка, которые станут доступны достаточно скоро.
Всем бобра ;)!

в рабочем чате как раз была переписка насчёт „дружелюбности” С++:

aaa:
13:52:06 I’m trying to figure out what a sentence can possibly mean when it has the words „C++” and „friendly” like right next to each other....

bbb:
13:54:25 It punches you in the nose, then kindly offers you a handkerchief to wipe up the blood, afterwards..

Сейчас я работаю над проектом на C# под Unity, который состоит из 4300 файлов с кодом. Это 25 мегабайт исходников! Время полной компиляции проекта на моем компьютере занимает 10 секунд. Берем пустой проект на Unreal, добавляем единственный объект с С++ классом. Вносим туда малейшее изменение — время компиляций и линковки до запуска, минимум 7 секунд на том же i7-8700.

Разве у C# не JIT компиляция? В смысле у вас могла быть включена компиляция только в промежуточный байт-код.

en.m.wikipedia.org/...​re)#Code_Execution_Engine

Именно так .Net и работает: проект компилируется в байт-код, а потом, при выполнении этого кода в запущенном приложении, происходит JIT.
Смысл примера был показать, что от внесения изменений в исходный C# код, до запуска приложения, происходят считанные секунды, независимо от сложности этих изменений.
В С++ проектах с ростом их размера значительно возрастает и время от внесения изменений до возможности увидеть их в работе. Следовательно C++ разработчикам Unreal проектов приходится ждать заметно дольше разработчиков на Unity. Да там есть Blueprint«ы, но визуальное «программирование» покрывает далеко не все потребности на серьезных проектах.

В .Net світі для підвищення перформансу використовують NGen наскільки я пам’ятаю (в останнє на C# писав роки 4 тому). І воно дійсно результативно тому що код тоді прямо на процесорів виконується без CLR. Але процес цей дуже тривалий.

Проблема лише в тому, що при зміні (апдейті) будь якої DLL яку використовує на будь-якому рівні залежності додаток процес треба перезапускати з початку. І виходить так що апдейт/патч ОС — ngen, оновлення будь-якої з програм яка встановила нову версію якоїсь з DLL — ngen, оновлення самої програми — ngen. І кожного разу це сотні (без перебільшень) DLL.

Просто згадалося.

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

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

зачем?

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

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

А вот если добавить заголовок функции в .h файл, но забыть в .cpp — об ошибке вы узнаете от линковщика. Информативностью его сообщения не отличаются.

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

4. Преобразование обычного класса в шаблонный.
...
В C# такая трансформация проводится достаточно просто.

То что есть в C# это не шаблоны, а дженерики. Там подстановка типа происходит во время выполнения, а не компиляции. Что накладывает серьезные ограничения на то, какие типы можно использовать. Например, обобщенные типы не поддерживаются для арифметических операций. Следовательно, не любой класс и не любой метод вы можете переделать в шаблонный. По этой же причине, в С++ отсутствует возможность определения шаблона только в одной единице компиляции (попытки сделать external templates были еще 25 лет назад, например в IBM VisualAge C++, но так и остались экспериментами).

5. Сборка проекта — линковка.
Если вы собираете проект через командную строку, как показывают во многих книгах или на онлайн-курсах...

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

Сейчас наличием IDE для языка никого не удивишь, но С/С++ программисты долго жили без этих благ цивилизации и хочется только снять шляпу перед их мужеством и упорством

Вы удивитесь, но для этого уже много десятилетий существуют такие инструменты, как CMake и GNU Autotools, с которыми не нужна никакая IDE, а кодить можно в Far-е. Раньше вполне можно было обходиться и просто Make, я сам вручную писал makefile-ы и все компилилось автоматом, а редактором у меня был что-то вроде SlickEdit или UltraEdit.

В С++ из языка С перекочевала особенность не инициализировать локальные переменные значениями по умолчанию.

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

А в остальном, все это напоминает причитания на тему «как сложно жить». С++ старый язык, и у него еще не самый плохой синтаксис. Сравните например с Objective C, на котором все еще пишут, а старых проектов вообще не счесть (под Мак конечно).

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

Вот именно, что в generic классах C#|Java есть возможность накладывать ограничения на шаблонный тип, что позволяет четко говорить компилятору, чем этот тип быть обязан, а не полагаться на принцип «слинковалось — тип подходит».
Да список ограничений достаточно небольшой и, действительно, нет возможности сказать, что такой то тип тоддерживает те или иные операторы, например математические. И это огорчает, не спорю. Но выкручиваться можно. Я привык к generic-классам C# и использую их массово. На сколько понял, в C++20 тоже добавили механизм ограничений, который позволит использовать шаблоны С++ более близким мне способом. Об этом я упомянул в статье.

А в остальном, все это напоминает причитания на тему «как сложно жить». С++ старый язык, и у него еще не самый плохой синтаксис. Сравните например с Objective C, на котором все еще пишут, а старых проектов вообще не счесть (под Мак конечно).

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

Якщо в Java/C# досі лише дженеріки, то це зовсім не шаблони. В першу чергу — по перформансу. А щодо обмежень на типи, то на рівні ідеології вони народилися певно разом із STL, а в мову довгий час не потрапляли, бо з цим ніхто не поспішав: ті, кому вони були потрібні, користувались собі спокійно Boost’ом чи самописними перевірками типу.

Тим не менше це типобезпечні конструкції, які дозволяють C# коду бути елегантним і зрозумілим в кожному випадку, коли вам потрібна схожа функціональність для схожих типів.

Та й нехай, але це й близько не шаблони C++, які за потужністю є ледь не окремою компайл-тайм мовою програмування, а за перформансом скомпільованого коду не поступаються коду без них.

На скільки я в курсі, в .Net швидкість роботи з generic класами не поступається швидкості роботи зі звичайними класами.

Наскільки я пам’ятаю, в C# та Java всі методи віртуальні (в термінах C++). Ясно, що ще повільніше й не зробиш, хіба рефлексію яку рантаймову замутити...

Вважаю, що рефлексію виправдано використовувати тільки для невеликого кола задач.
Без generic’ів шарп не був би такою потужної і елегантною мовою, для свого кола задач, певна справа.

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

Я не знаю что там добавили в C++20, а начиная с C++11 для этих целей служит механизм SFINAE. Который позволяет реализовать ограничения и/или выбор подходящего шаблона в зависимости от типа-параметра. Да, с непривычки это выглядит сложно, и пишется далеко не сразу, но оно и надо не каждый день.

SFINAE — уродливый костыль, добавляющий головной боли не только темплейтным гуру-программистам во время написания/чтения шаблонного кода, но и клиентам этого кода в случае подстановки неправильных параметров. Попробуй ещё, не будучи задротом темплейтов, быстро разберись, что пошло не так, глядя на сообщения об ошибках компилятора в очередном SFINAEнарнике.
Когда ты темплейтный задрот (вроде меня), то да, это не проблема. Но когда ты обычный программист, решающий своим кодом бизнесовые задачи и просто пользующийся темплейтными библиотеками в этих целях, ошибки основанных на SFINAE темплейтов могут взорвать мозг.
Концепты призваны заменить этот костыль на нормальный механизм, понятный простым смертным.

Сам синтаксис «template<typename T>» (или «template<class T>») — это примерно как если бы все обычные функции принимали только аргументы типа Object* (где Object — абстрактный тип, от которого наследуются все возможные типы аргументов), и дальше что там за параметр должен быть на самом деле, пусть реализация функции разруливает сама набором динамик_кастов.

Возможность вместо слова "template"/"class" написать наконец что-то осмысленное (указать «тип» шаблонного параметра, т.е. его концепт, например «template<Iterable T>») — это возможность возложить все эти проверки на плечи компилятора и в то же время получить куда более читабельный код. Это офигеть какое облегчение как для тех, кто пишет темплейты, так и для тех, кто ими пользуется.

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

SFINAE чудово працював й в С++2003, щодо С++98 хіба не впевнений, бо не гамав його ніколи нормально.

Мабуть, малося на увазі, що C++11 його популяризував, додавши тайп_трейти (і зокрема std::enable_if) до стандартної бібліотеки.

Ну таке, до стандартної бібліотеки воно багато років жило у бусті

но оно и надо не каждый день.

лично мне эта фича нужна прямо таки, каждый день.

Про время компиляции какой-то пи&@%жь )

Кстати, сегодня была возможность сравнить скорость компиляции этого же проекта на чуть другой конфигурации.
Диск похожий, оперативки 32Гб DDR3 (у меня 16Гб DDR4), проц i7-6700(4 ядра 8 потоков) против моего i7-8700(6 ядер 12 потоков). Скорость сборки у этого компа оказалась где-то в 1.5-2 раза ниже.
Видать количество потоков тоже имеет значение.
В случае C++ это понятно, скорость легко скалируется на потоки путем независимой компиляции каждого файла. В случае C# это было не так очевидно.

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

Самая первая причина — возраст и зоопарк стандартов(цэ, цэ с классами, 03, 11, 14, 17, 20). Вводя n фич к языку из m фич мы получаем n * m вариантов взаимодействия новых фич со старыми, в С++, перегруженным фичами, эта область взаимодействия вряд ли просматривается полностью комитетчиками, а рядовыми программистами полностью. Учитывая что плюсики это язык в который всё вбрасывают и ничего не выбрасывают, область теневая там достаточно больших размеров.

Вторая причина — отсутствие максимального усложнения написать программу неправильно. Язык, программы на котором претендуют на надёжность должен исключать наиболее широкие классы ошибок. С++ этого делать даже не пытается, а в некоторых местах подначивает к ошибкам. В частности, любые попытки рассуждать о поведении программы ломаются если у вас есть в программе хотя бы одно UB, потому что это самое UB может сотворить что угодно без любой возможности это формально предсказать. Вместе с этим в С++ компиле нету UB-чекера. Вместе с этим в С++ нет ещё много каких чекеров — от детектора блокировок до прувера того что память не потекла, и это фатальный недостаток. Как следствие — discipline driven development(борьба с языком) это просто единственная возможная стратегия написать что — то возможное.

Третья — экстенсивное использование долговременной памяти программиста. Чтобы успешно писать на плюсиках надо точно и совершенно безотказно помнить некоторые их приколы, которые к тому же не собраны в какую-либо лаконичную С+±приколологию, и вместе с тем очень больно бьют по голове, если о них забываешь. Особенно это сказывается во всяких шаблончиках-макросах-многопоточке. Как следствие — невозможность отлучиться от проекта безболезненно, править код на отморозе, и жуткая инертность с апгрейдами — это же новое учить и запоминать.

Это основные причины — очень много чего — их следствия.
И да языки, в которых эти все недостатки редуцированы до минимума — есть.

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

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

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

Почему это не пытается? В той же студии уже несколько лет как есть вполне годный анализатор кода, который в отсутствии денег у конторы на PVS Studio например, вполне делает свою работу. И кто сказал, что программы «претендуют на надежность»? Они претендуют на максимальную производительность и контроль ресурсов, а надежность это вопрос скорее техник разработки и тестирования.

Статистический анализатор кода

PVS Studio например

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

и контроль ресурсов

Вот например жава может в контроль памяти, потому что в ней есть гц. А в контроль памяти С++ не может, потому что я могу написать int* v = new int[20]; v = NULL; и контроль потерян. Вместе с контролем памяти ломается попытка контроля всего остального.

а надежность это вопрос скорее техник разработки и тестирования.

так в этом же всё и дело, сколько эта разработка и тестирование занимает.

так в этом же всё и дело, сколько эта разработка и тестирование занимает.

C++ применяют там, где требования к эффективному использованию ресурсов, превышают (некоторые) неудобства разработки. И потом, на голом С++ никто не пишет — всегда есть какой-нибудь фреймворк. У кого-то игровые движки, у кого-то Boost, у нас вот Qt. А если это Qt, то разработка вообще мало отличается от .NET, ну может компилится дольше.

я могу написать int* v = new int[20]; v = NULL

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

Я завжди на code review завертаю код з new/delete — є shared/unique вказівники, є vector та array і випадки коли треба пам’ять менеджети вручну настільки рідкісні, що я хочу знати і розуміти кожен з них.

Хоча може дійсно є такий клас задач де це треба робити саме так?

на code review

код ревью это хорошо но нужно чтобы в этом процессе человек участие принимал по минмуму и тем более не делал это ручками.

есть валгринды, адрес-аналайзеры, встроенные в компилятор и дофига всего другого (ну, да, это не на винде)

есть валгринды, адрес-аналайзеры, встроенные в компилятор и дофига всего другого

но это всё ковбойские unsound поделки для победятл-драйвен девелопмент.

www.youtube.com/watch?v=QbnVELXf5RQ

Use DOTs и будет тебе счастье на Юнити

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

В данный момент мне чрезмерно комфортно быть C# разработчиком под Unity и хочется новых вызовов в лице C++ с Unreal Engine.

Між іншим, тут так і хочеться процитувати автора C++: „There are only two kinds of languages: the ones people complain about and the ones nobody uses”

есть специальный язык для ̶m̶o̶n̶k̶e̶y̶ ̶c̶o̶d̶e̶r̶s̶ молодых и одаренных программистов которые не осилили с++
habr.com/ru/post/344356

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

Ждем Вашего Docker на плюсах.
И Kubernetes пожалуйста. И так далее и тому подобное.

задолго до докера был виртуал бокс и он написан на смеси с, с++ и ассемблера

который относиться совсем к другому классу решений
Вспомни Вы jail для чертенка, zones в солярке, OpenVZ для пингвина, было бы куда адекватнее.
Видимо это такая форма плохого настроения, не знаю, потому что я не понимаю зачем Вы
в ветке C++ харкнули в решение и его пользователей, которое не позиционировалось как альтернатива C++, а скорее как нишевая альтернатива C,
Но при этом в своей нише используется как база довольно значительной части современного backend и на котором решаются очень взрослые задачи.

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

который относиться совсем к другому классу решений
задолго до докера был

chroot

Хорошая попытка, но нет. )

Серьезно. У С++ есть достаточно много криво реализованных, переусложненных или недоделанных фич, овиринжиниринга на ровном месте и тд.
Сесть и быстро переделать все «по-человечески» нельзя, потому что это сломает обратную совместимость с существующим кодом, и в результате получится новый язык, который будет никому не нужен. Александреску вон запилил свой D, и где он теперь?

Потому комитет делает то, что может: вводит новые фичи на замену совсем уж сумрачным легаси-костылям; потихоньку депрекейтит/выпиливает из стандарта то, что можно убрать без радикального ломания всего языка и тд.
С другой стороны ему помогают производители компиляторов: улучшают диагностику ошибок; изобретают новые инструменты анализа кода, которые позволяют свести к минимуму возможности для «стрельбы в ногу» и тд.

Idiomatic C++ на сколь-либо современных проектах в 2020 году кардинально отличается от того, что было 20 и даже 10 лет назад. Рядовой гребец в прикладном коде достаточно редко сталкивается с темными сторонами языка (при правильно налаженных процессах, конечно). Берем из интернета набор guidelines, прописываем к себе в кодстайл, форсим проверку стиля для всех коммитов в CI (clang-tidy/sanitizers/whatever), и, внезапно, абсолютное большинство «стандартных» проблем отсеивается автоматически. В итоге ситуация изнутри выглядит далеко не так страшно, как кажется сторонним наблюдателям после чтения таких вот статей.

Проблема этой статьи (и большинства подобных ей) в том, что вместо действительно серьезных недостатков языка в ней в ассортименте представлены:

  • Entry-level штуки, которые через пару месяцев использования языка перестаешь замечать и делаешь на автомате, вроде необходимости писать #pragma once в хедерах или указания public при наследовании классов;
  • Истории про то, как можно подергать язык за устаревшие костыли, для которых давно сделаны более «правильные» аналоги, и посмотреть, как он эпично валится при этом (вроде «прикольчиков» из секции про круглые скобки и классы);
  • Легкие передергивания, добавленные для нагнетания полемического угара («посмотрите, как они в консоли каждый раз перечисляют все 100 файлов, которые нужно слинковать, ну ващщщеееее!», или «у вас вот тут ошибка неочевидная, новичку без гугла непонятно»);
  • Просто questionable утверждения, к которым для солидности дописан пассаж про «современный подход одобряет такое», чтобы читатель испугался и не усомнился. )
  • Если воспринимать написанное с точки зрения «я несколько месяцев покодил на С++, и вот что я думаю» — получилась добротно сделанная (хоть и достаточно шаблонная) статья. Но делать каких-то глобальных выводов о языке на ее основе не стоит. Немного комментариев/уточнений ниже:
    Компиляция и линковка
    Не буду комментировать подробно, потому что модули решают бОльшую часть описанных проблем. Да, даже #pragma once писать не нужно. )
    Сейчас я работаю над проектом на C# под Unity, который состоит из 4300 файлов с кодом. Это 25 мегабайт исходников! Время полной компиляции проекта на моем компьютере занимает 10 секунд.
    Сорян, но не верю. C#, конечно, по очевидным причинам собирается заметно быстрее, но не прям настолько. Вероятно это была не совсем полная компиляция. Так-то я тоже могу включить ccache и вворачивать для красного словца, как у меня гигантские проекты пересобираются с нуля за секунды. )
    2. Второй прикольчик связан с правилом компилятора «все, что выглядит как декларация функции, будет считаться декларацией функции».
    Just use uniform initialization syntax, Luke.ClassA obj1{10}; ClassB obj2{true, 2}; ClassC obj3{};Его запилили в том числе и для избавления от всех описанных в этом пункте «прикольчиков».
    Получим такое сообщение об ошибке
    main.obj : error LNK2019: unresolved external symbol «public: int __thiscall A::f(void)» (?f@A@@QAEHXZ) referenced in function _main
    А что не так? «Ты вызываешь такой-то метод из функции мейн, но я не нашел его определение», как тут можно расписать еще подробнее?Более интересная ситуация получается, если сделать все виртуальные методы класса инлайновыми. Тогда линкер может пожаловаться на ненайденную vtable, и тут действительно начинающему может быть не совсем очевидно, чего от него хотят. Придется разок загуглить ошибку и запомнить причину со стековерфлоу.
    Клик по ошибке в IDE не подсвечивает строчку
    Действительно, как страшно жить ) Линкер не может показать тут номер строки, потому что у него в общем случае нет доступа к исходникам. IDE технически может ткнуть пальцем в объявление метода в хедере; почему она этого не делает — это уж точно не проблема языка.
    Эта необходимость держать два файла синхронизированными заставляет постоянно копипастить куски кода, что в мире программирования, как правило, порицается.
    Немного странная претензия: разделение интерфейса и реализации придумано задолго до С++, и используется сейчас практически во всех статически типизированных языках. В этом вашем С# вон точно так же нужно «копипастить» сигнатуру метода из интерфейса, и что?К тому же все строго добровольно: хочешь спрятать реализацию класса, чтобы не нужно было ребилдить всех причастных при ее изменении — «копипастишь»; не хочешь копипастить, и готов мириться с ребилдами — кладешь все в хедер; боишься за время компиляции — см. модули.
    Правда, громоздкий вариант выноса определения функций за пределы шаблонного класса тоже часто встречается. Уж не знаю, какие преимущества он дает.
    Он позволяет мне быстро просмотреть интерфейс класса и не отвлекаться на пролистывание реализации его методов, например. А еще реализацию можно вынести из заголовка в отдельный файл, и инклудить его только тогда, когда это действительно нужно.
    Если вы собираете проект через командную строку, как показывают во многих книгах или на онлайн-курсах, то без ухищрений процесс сборки сложно назвать увлекательным.
    Если вы набираете текст на клавиатуре одним пальцем, то процесс получается тоже не очень увлекательным. Потому вы так не делаете, верно? )В книгах показывают, как собирать хелловорлды из командной строки, чтобы дать читателю хотя бы базовое понимание того, что происходит во время компиляции. Чтобы он не впадал в прострацию при виде сообщения про unresolved external от линкера, например. ) Никто не учит собирать так реальные проекты.
    программисты создают так называемые makefiles, где заранее определяют, из чего состоит проект и как его собирать. Работа с ними требует определенных знаний.
    В простейшем случае не требует, достаточно скопипастить две строчки из той самой книжки, которая учила собирать хелловорлд. Более сложные случаи будут разруливаться либо в IDE, либо в хайлевельных системах сборки типа CMake. Которые уже таки да, «требуют определенных знаний», как и msbuild для C#, например )
    Сейчас наличием IDE для языка никого не удивишь, но С/С++ программисты долго жили без этих благ цивилизации
    Когда это они успели? ) Первые коммерческие компиляторы С++ эволюционировали из компиляторов C, которые на тот момент уже поставлялись вместе с зародышами IDE (см. Turbo C). По крайней мере описанную выше проблему с необходимостью ручного добавления новых файлов в проект они точно умели решать.
    Кроме того, в C# мне нравится располагать рядом приватные данные и публичные методы доступа к этим данным
    Явная же вкусовщина, ну. Мне нравится наоборот, но это ж не повод делать далеко идущие выводы о языках/современных подходах.Да и никто не мешает писать на С++, как на С#, если так уж хочется (а верно ли обратное, кстати?):public: float GetSomeValue() { return _someValue; }private: float _someValue;public: еще один геттер;
    Модификатор доступа как часть заголовка каждого метода и поля класса дает большую гибкость в организации кода и повышает его читаемость. Думаю, поэтому в современных языках такой подход — распространенная практика.
    Пассаж про «большую гибкость» выглядит достаточно иронично в свете предыдущего абзаца, не правда ли? )
    в стандарт добавлено ключевое слово override, которое можно встретить и в других языках, но в C++ оно, в угоду обратной совместимости, полагаю, не есть обязательным.
    Верно. Потому все современные компиляторы умеют выдавать ворнинги при его отсутствии; эти ворнинги можно прокачать до ошибок и сделать override обязательным у себя на проекте.
    но посильной задачей для компилятора было бы отлавливать все ситуации наследование классов от базовых с невиртуальными деструкторами и выводить сообщение об ошибке.
    Они, в общем, так и делают. Проблема приведенного фрагмента кода в том, что в таком виде он далеко не всегда является ошибкой. Я могу сделать иерархию классов, и передавать по коду указатель на наследника в виде base* туда, где нужны только данные родителя. Если у меня в базовом классе нет виртуальных методов, и я гарантирую, что delete всегда вызывается с правильным типом, я могу не делать деструктор виртуальным, и таким образом избежать появления vtable у рассматриваемых классов. Это, конечно, достаточно небезопасное решение, к которому приходится прибегать только в исключительных случаях, но компилятору приходится его учитывать в любом случае. ВИначе пришлось бы выдавать ворнинги на каждое обращение к такому наследнику через указатель на базу, чего делать нельзя из тех же соображений обратной совместимости.TL;DR: если в приведенном примере добавить хотя бы один виртуальный метод в базовый класс — компилятор поймет, что это почти точно ошибка, и выдаст ворнинг (гцц/шланг точно, студии под рукой нет, чтобы проверить).
    Перегрузка методов
    В С++11 хотели запилить возможность запрещать «прятать» методы родителя (это включалось бы атрибутом у класса, чтобы не ломать обратную совместимость). Но пропозал не прошел, потому нужно смотреть на ворнинги.
    Неявный вызов конструктора
    Тут соглашусь, я бы тоже сделал наоборот (explicit by default, implicit on demand). Но, опять же, обратная совместимость.
    В С++ из языка С перекочевала особенность не инициализировать локальные переменные значениями по умолчанию.
    В 2020 ворнинги и санитайзеры решили эту проблему. К тому же в шланге вон запилили расширение, которое позволяет автоматически инициализировать все локальные переменные нулем. Боюсь, скоро начитаются жалоб и включат его по умолчанию (
    С полями классов ситуация еще хуже. Они могут быть автоматически инициализированными дефолтными значениями, а могут и не быть — зависит от ситуации, в какой создается объект. Правила инициализации достаточно сложные, чтобы их записать в короткой статье.
    Правила инициализации достаточно простые: если у вас в классе есть поля фундаментальных типов (int, bool, «чистый» указатель и тд) — их всегда нужно инициализировать вручную. Это все, что вам нужно знать в 99.99% случаев.
    язык провоцирует использовать списки инициализации для всех полей.
    Ну не знаааю, лично меня язык провоцирует инициализировать большинство полей прямо в определении класса (int field = 123;). Списки инициализации нужны для вызова конструкторов базовых классов, и для полей, у которых инициализация нетривиальная и ее не хочется класть в хедер.
    К сожалению, никто не подскажет, если после вызова конструктора у вас все же останутся неинициализированные поля.
    Как и с локальными переменными выше: в тривиальных случаях компилятор выдаст ворнинг при обращении к такому полю; в нетривиальных сагрится санитайзер.
    Поэтому либо никогда не используйте значение полей как базовое для других полей в списке инициализации, либо никогда не меняйте порядок их объявления в теле класса!
    Либо читайте, что вам говорит компилятор: «warning: field2 will be initialized after field1»
    И мне не понятно, зачем были придуманы такие сложности, которых в других языках нет.
    Потому что во время, когда придумывались эти сложности, других языков еще в проекте не было ) (см. выше про обратную совместимость)
    Например, не гарантируется, что порядок вычисления значений параметров при вызове метода будет строгим.
    Есть такое. В общем случае наиболее эффективно считать параметры в том порядке, в котором они должны попасть в стек; этот порядок зависит от calling convention; соответственно если его строго задать в языке, компилятору придется генерить чуть менее эффективный код. 30 лет назад это было достаточным аргументом. Сейчас уже пара лишних инструкций погоды не сделают, потому в С++17 для многих случаев порядок наконец задали; думаю, в одном из следующих стандартов зададут и для всего остального. В остальном проблем с совместимостью между современными версиями компиляторов в 2020 году практически не осталось. Они есть, но в рядовом прикладном коде вы с ними не столкнетесь.
    На личном опыте неоднократно сталкивался с тем, что достаточно простой код, который запускается и корректно работает на MSVC, даже не компилируется при использовании gcc и наоборот.
    Тут я вынужден реквестировать пруфы с примерами достаточно простого кода, который корректен с точки зрения языка, и при этом так себя ведет на современных версиях упомянутых компиляторов. Только из личного опыта, а не из багрепортов в интернете, пожалуйста. :)
    Четкие правила — залог успеха
    Приведенный там пример с shared_ptr больше не актуален: начиная с С++17 параметры вычисляются всегда строго последовательно (хоть и в неопределенном порядке), см. линк на PDFку выше.
Александреску вон запилил свой D, и где он теперь?

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

І дуже добре, що ніхто не займався і D майже канув в Лету. Після пари спроб щось на ньому написати він в мене викликав стійку відразу. Думка про те, що на роботі хтось зненацька скаже «а новий проект ми будеми писати на D» в мене викликає жах.

Тобі добре, в тебе викликав відразу. У багатьох інших людей D викликав іншу реакцію.
Як то кажуть, вкусовщіна.
Гірше б не було, якби ця мова теж стала відносно розповсюдженою. Плюсовики, які не захотіли переходити, без роботи б все одно не залишились.

Між іншим, іноді дійсно цікаво, чому може як зараз кажуть «не зайти» якась нова мова. Раціонального тут здається зовсім мало. В мене особисто ObjC, D, rust дуже не зайшли, а python, lua, perl зайшли дуже непогано. C#, Java, JavaScript та go сприймаються нормально, без восторгів але й без відрази. Мабуть щось на підсвідомости — може якась неприємна мені людина похвалила. Ось про D що пам’ятаю: навіть простий приклад іноді призводив до internal error в компіляторі, відсутність IDE, два альтернативних рантайми та якісь вже не пам’ятаю особливості роботи GC. Може зараз вже все й пофіксили, але щось немає бажання ще раз до нього повертатися.

А Objective-C чим не сподобався? Як на мене одна з найбільш елегантних C-подібних мов.

причём ооп там гораздо более ооп чем в тех же ж «плюсах» ))

да, можно слать сообщения в /dev/null )

Багато хто так каже. :) Синтаксис не подобається. alloc init withSomething: andSomething: andOneMoreThing: andEvenWithMore: та купа [ ].

Синтаксис скажем так спорный местами, плюс надо знать особенности рантайма, т.к. ObjC это нишевый язык для macos/ios, и практически не развит на других платформах. Но сама концепция «вызов функции суть передача сообщения объекту» это ООП в чистом, каноническом виде, и реализация не подвела. Жаль только, что язык не получил развития, а то мог бы занимать сегодня место C#.

Синтаксически ObjectiveC, ИМХО, ужасен.
Когда-то попробовал к нему прикоснуться и отсахнулся, как от дурно пахнущей кучи.
Мне кажется не просто так Swift пришел ему на смену и последние годы успешно вытесняет первый с рынка.

А мені D сподобався. Там є цікаві ідеї, наприклад: const означає, що ти не можеш міняти значення змінної, а immutable — значить ніхто не може її міняти взагалі.

Відверто кажучи, я в цьому не бачу нічого революційного. Ось що в D добре, це метапрограмування на тому ж D, а не на іншій езотеричній декларативній мові (натякаю на шаблони С++). Але загальна забагованість компілятора завадила мені насолодитися метапрограмуванням на D. :)

Вот бы они договорились с Unreal и сделали там поддержку из коробки D — мне кажется это добавило бы популярности, что D, что Анрилу.

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

Не знаю, на сколько правдоподобно это прозвучит, но те, кто со мной работал, а таких пару сотен наберется точно, не дадут мне соврать, что я всегда искренен. Так вот, я не пытался написать статью похожую на другие, которые хают С++. Более того, я никогда не читал подобные статьи и потому не знаю штампов, которые в таких случаях используются. Большинство информации, которую я получил в свое время — почерпнута из книг: Скитт, Альбахари, Рихтер, Вагнер, Хейлсберг по C#; Шилд, Эккель, Прата, Мейерс, Александреску по С++. Python учил сразу на проекте будучи серверным программистом игровой логики. По Rust прочел 3 книги, по D одну от Александреску. Подобных статей раньше не писал — это проба пера, может и последняя :). Я хочу поделиться опытом с новичками и почерпнуть новый из общения. Ваш коментарий дал очень много полезной и новой информации для меня. Спасибо вам большое.

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

Темы: C++, junior, tech, разработка

,
можно увидеть, что ориентирована она была в первую очередь на тех, кто с языком знаком так себе. Более то, в мотивирующей части я перечислил целевую аудиторию более точно. Так вот для этой целевой категории совсем неважно почему вышло так, им важно знать, что с этим делать.
Мне достаточно часто приходится общаться со студентами и нередко они считают, что нужно начинать с самого мощного инструмента — зачем, мол, тратить время на «промежуточное». Для таких я пытался показать, что количество информации и проблем, с которыми придется столкнуться, в разы выше языков, у которых проблем обратной совместимости нет.
Для людей, который много пишут на других языках и которые хотят более сложного, а так же для тех, кто что-то писал на плюсах в институте и думает этого достаточно, чтобы писать у себя в резюме «знаю С++», я хотел показать, что не зря у С++ самая большая спецификация языка — этот язык действительно объемен, сложен, у него много подводных камней, а дальше уже каждому решать, нужен ли ему этот головняк. Мне вот, как ни странно, нужен, о чем и написано в выводах.
Жаль, что для некоторых опытных С++ программеров статья стала красной тряпкой — мне казалось, что во вступлении достаточно четко написано, что никого из них я учить жизни не собираюсь.

Idiomatic C++ на сколь-либо современных проектах в 2020 году кардинально отличается от того, что было 20 и даже 10 лет назад. Рядовой гребец в прикладном коде достаточно редко сталкивается с темными сторонами языка (при правильно налаженных процессах, конечно). Берем из интернета набор guidelines, прописываем к себе в кодстайл, форсим проверку стиля для всех коммитов в CI (clang-tidy/sanitizers/whatever), и, внезапно, абсолютное большинство «стандартных» проблем отсеивается автоматически. В итоге ситуация изнутри выглядит далеко не так страшно, как кажется сторонним наблюдателям после чтения таких вот статей.

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

Сорян, но не верю. C#, конечно, по очевидным причинам собирается заметно быстрее, но не прям настолько. Вероятно это была не совсем полная компиляция. Так-то я тоже могу включить ccache и вворачивать для красного словца, как у меня гигантские проекты пересобираются с нуля за секунды. )

Тем не менее из коробки у меня все работает именно так drive.google.com/...​ZNEyD14kVAjA5aScDuEcDe3zQ . Хоть и не очень много, но на прошлом проекте мне приходилось писать на С++ и время от внесения изменений до возможности их проверить шло на минуты даже при самых простых изменениях.

Just use uniform initialization syntax, Luke.

Ок, буду знать, хоть и выглядит это непривычно.

Действительно, как страшно жить ) Линкер не может показать тут номер строки, потому что у него в общем случае нет доступа к исходникам. IDE технически может ткнуть пальцем в объявление метода в хедере; почему она этого не делает — это уж точно не проблема языка.

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

Когда это они успели? ) Первые коммерческие компиляторы С++ эволюционировали из компиляторов C, которые на тот момент уже поставлялись вместе с зародышами IDE (см. Turbo C).

Здесь немного погрячился. Но долгое время IDE для C++ действительно были скорее зародышами. Та же студия без платных плагинов очень уступала себе же при работе с C#. Хорошо, что начиная с 2015й студии ее допилили до состояния, что решарперы уже особо и не нужны.

Явная же вкусовщина, ну. Мне нравится наоборот, но это ж не повод делать далеко идущие выводы о языках/современных подходах.Да и никто не мешает писать на С++, как на С#, если так уж хочется (а верно ли обратное, кстати?):public: float GetSomeValue() { return _someValue; }private: float _someValue;public: еще один геттер;
Пассаж про «большую гибкость» выглядит достаточно иронично в свете предыдущего абзаца, не правда ли? )

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

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

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

Правила инициализации достаточно простые: если у вас в классе есть поля фундаментальных типов (int, bool, «чистый» указатель и тд) — их всегда нужно инициализировать вручную. Это все, что вам нужно знать в 99.99% случаев.

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

Ну не знаааю, лично меня язык провоцирует инициализировать большинство полей прямо в определении класса (int field = 123;). Списки инициализации нужны для вызова конструкторов базовых классов, и для полей, у которых инициализация нетривиальная и ее не хочется класть в хедер.

Такой вывод я сделал снова же на основании книги Мейерса за 2006й год. Вполне вероятно, что сейчас это стало неактуальным. Если присваивать значения полям в конструкторе класса считается нормальной практикой — вы меня успокоили.

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

Не знал, спасибо.

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

Например решал задания на онлайн курсах и в студии получал один результат, а на сайте при комите решения — другой. В коментах народ рекомендовал использовать что-то типа www.onlinegdb.com/online_c _compiler и там результат так же не совпадал с результатом студии, но в точности совпадал с результатом на сайте курсов. Еще были ситуации, когда код, который заапрувился, так как был изначально проверен на www.onlinegdb.com/online_c _compiler вообще не компилировался в студии.
Нужно искать, чтобы показать конкретнее.

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

Я так сходу не вспомню какой-то прям всеобъемлющей ссылки. Если это одиночный пет-проект — выкручиваем ворнинги и code analysis компилятора на максимум, подключаем clang-tidy, гоняем тесты под санитайзерами, соблюдаем cppcoreguidelines. Можно еще обмазаться каким-то pvs-studio, если совсем скучно на карантине. ) Если проект на гитхабе — можно автоматизировать все это в travis/appveyor/что там сейчас еще модно. Для мелких/средних проектов должно хватить.

Новчику ведь все равно почему он не может простым кликом увидеть строку кода.

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

Ну согласитесь, что такая запись все же противоречит идеологии C++ и выглядит не очень.

Я не знаю, что такое «идеология С++» в данном контексте. Но от шарповской эта запись отличается только лишним двоеточием. Этого достаточно, чтобы «выглядеть не очень»? Ну окей, «#define public public:», любой каприз, как говорится )
Ирония тут в том, что С#, который позволяет ровно один способ записи (повторять public каждый раз), Вы нахваливаете за гибкость и промоушен современных стандартов. А С++, позволяющий писать как нравится (в том числе и «шарповским» способом!), почему-то ругаете за эту возможность. Несправедливо! )

Ну и в каких еще популярных современных языках используется такой же способ?

В руби, например. Я не настолько хорошо помню синтаксис всех остальных языков, чтобы приводить больше примеров, а гуглить лень, сорян ) Энивей, «раз так никто не делает, значит это плохо» — очень плохой аргумент сам по себе.

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

Делаем деструктор protected, если это перестает использоваться только локально.

О сложных правилах инициализации пишет Мейерс, я ему верю.

Я тоже ему верю ) Да, они достаточно сложные, если открыть стандарт и начать вникать во все корнер кейсы. И, тем не менее, если мы говорим только о полях классов — я продолжаю настаивать на том, что новичку, который не парится, в 99.99% случаев будет достаточно того одного правила, которое я приводил выше.

Такой вывод я сделал снова же на основании книги Мейерса за 2006й год. Вполне вероятно, что сейчас это стало неактуальным. Если присваивать значения полям в конструкторе класса считается нормальной практикой — вы меня успокоили.

Определение класса, не конструктор:

class c1 {
  int field1 = 123;
  std::vector<int> field2 = {1, 2, 3};
};
Так можно делать начиная с С++11, потому да, в 2006 году Мейерс об этом еще не писал.
Вы недосмотрели — там речь шла о шаблонном классе, код реализации методов которого не получится вынести в отдельный файл.

В некоторых случаях получится. Если мне заранее известен список типов, которые будут использоваться с этим шаблоном, я могу вынести реализацию методов в .cpp и добавить там explicit instantiations для этих типов. Если я пилю библиотеку, шаблоны которой будут использовать другие люди со своими типами — могу вынести реализацию в template_impl.h, они его у себя заинклудят в одном месте и добавят нужные инстанциации.
Это, кстати, заодно решит и проблему из подзаголовка «компиляция шаблонных классов».

Кстати, посмотреть только интерфейс класса можно средствами IDE — вполне читаемо.

Обычно можно, да. Но что делать, если я хочу быстро заценить интерфейс какой-то библиотеки прямо с гитхаба/геррита/whatever без клонирования и открытия в IDE? Или если у IDE ломается парсер на каком-то проекте (из-за отсутствия зависимостей, например)?

Я так сходу не вспомню какой-то прям всеобъемлющей ссылки. Если это одиночный пет-проект — выкручиваем ворнинги и code analysis компилятора на максимум, подключаем clang-tidy, гоняем тесты под санитайзерами, соблюдаем cppcoreguidelines. Можно еще обмазаться каким-то pvs-studio, если совсем скучно на карантине. ) Если проект на гитхабе — можно автоматизировать все это в travis/appveyor/что там сейчас еще модно. Для мелких/средних проектов должно хватить.

Отлично, покопаю в этом направлении. Отдельное спасибо за cppcoreguidelines, подозревал, что что-то такое есть, но никогда не слышал :).

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

Формально да. По факту все это оносится к удобству работы с языком. И я помню свое негодование, когда первый раз осознал, что от IDE помощи я в этом случае не получу :).

Ирония тут в том, что С#, который позволяет ровно один способ записи (повторять public каждый раз), Вы нахваливаете за гибкость и промоушен современных стандартов. А С++, позволяющий писать как нравится (в том числе и «шарповским» способом!), почему-то ругаете за эту возможность. Несправедливо! )

Сложно что-то несубъективное возразить :). Но скорее всего, если у вас кто-то на проекте начнет так писать — вы ему быстренько расскажете о корпоративном code style ;). Субъективно мне больше нравится (привычно?) как реализовано в шарпе и иже с ними. Интересно почитать почему именно такой способ стал популярнее и на сколько я угадал со своими предположениями.

Делаем деструктор protected

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

Так можно делать начиная с С++11, потому да, в 2006 году Мейерс об этом еще не писал.

Я интересовался именно конструктором, когда значения полям присваиваются из параметров.

struct A
{
public:
int Y;
int X;
int Z; //забытые переменные остаются на вашей совести

A(int value) :
X(value + 1),
Y(2*X) //в списках инициализации опасно полагаться на значения других полей
{
//классический для других языков способ инициализации в С++ считается не эффективным
//X = value + 1;
//Y = 2*X;
//Z = 0;
}
};

на сколько корректным считается инициализировать поля класса в конструкторе именно «классическим способом»? Мэйерс писал, что это менее эффективно — уже пофиксили?

По шаблона вопрос: как все же каноничнее поступать (делают чаще) — писать код внутри шаблонного класса или выносить за его пределы?

Спасибо за ликбез — я с удовольствием впитываю ;)

сколько корректным считается инициализировать поля класса в конструкторе именно «классическим способом»? Мэйерс писал, что это менее эффективно — уже пофиксили?

См. мой первый комментарий в этой теме. Это невозможно пофиксить, потому что это не сломано.
В одном случае выполняется инициализация (вызов конструктора с определёнными параметрами для объекта, который в этот самый момент времени создаётся), в другом — присваивание (изменение значения ранее созданного объекта, для которого был вызван дефолтный конструктор).
Вещи принципиально разные, и да: сначала создавать объект с одним состоянием, а затем присваивать ему другое состояние, будет в общем случае не эффективно. В частном же случае с примитивными типами (int, float, голые указатели, ...) разницы не будет никакой: их можно инициализировать и в теле. Хотя я бы предпочёл всё делать в списках инициализации, для консистентности.

По шаблона вопрос: как все же каноничнее поступать (делают чаще) — писать код внутри шаблонного класса или выносить за его пределы?

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

Формально да. По факту все это оносится к удобству работы с языком.

Получается я могу теперь обоснованно ругать C# (как язык) за косяки студии? Rly? )

Если есть причина — можно ругать окружение созданное Микрософт для языка ;). Так объективно Resharper добавлял студии кучу возможностей, которой она не обладала без него. В результате студию допилили до состояния, что вполне можно жить на чистой студии, допускаю критика возимела действие.
Я понимаю ваш довод. Да, к языку инструменты прямого отношения не имеют. А есть ли IDE, в которых ошибки линковки более информативны чем в студии?

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

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

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

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

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

Все так, но насчет этого:

Just use uniform initialization syntax, Luke

не соглашусь. Конструкция uniform initialization выглядит чуждой для языка, особенно когда пишут в стиле member{value}, это какая-то дичь вообще. И только добавляет путаницы — раньше так записывалась инициализация структур/массивов, потом придумали initalization_list, но там нужен был оператор = и это было ок. Так и сейчас, можно было придумать какой-нибудь оператор := для случаев, где закрыт обычный copy assignment. Это конечно мой личный субъективизм, но я считаю, что использовать эту штуку надо, только когда привычный синтаксис не работает (например в случае atomic<>). И не тулить где попало, как тот же auto или decltype, тьху.

И насчет этого:

Линкер не может показать тут номер строки, потому что у него в общем случае нет доступа к исходникам.

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

В очередной раз с C++ работал пару месяцев назад, когда взялся разбираться с Unreal Engine. И поразился

В том и проблема: C++ адекватный язык для тех, кто на нём пишет постоянно. Для тех, кому надо время от времени на плюсах что-то пописать, а затем вернуться к другому (основному) стеку, это ужасный язык. Потому что нужно помнить много вещей, которые вылетают из головы, если долго на нём не писать.

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

Здесь особо нечего осмысливать. «Исторически сложилось», наследие C, ряд сомнительных или явно неудачных решений в дизайне самого C++ (обосновывается по большей части тем, что комитет по стандартизации — сборная солянка энтузиастов из разных компаний, где каждый тянет одеяло на себя и пытается пропихнуть нравящиеся субъективно ему вещи; плюсы не развиваются одной конкретной компанией, как джава или шарп), а также необходимость сохранять обратную совместимость, запрещающая выкидывать ненужные вещи и упрощать язык. Всё.

2. Второй прикольчик связан с правилом компилятора «все, что выглядит как декларация функции, будет считаться декларацией функции»

Классика. «Most vexing parse», ещё в одной из первых книжек Майерса было.
В конце не хватает совета использовать по возможности для инициализации объектов классов фигурные скобки вместо квадратных («MyObject object{init};»), а для кастов между типами — static_cast вместо сишных вариантов.

3. Добавление новых функций, а также изменение сигнатуры существующих.
об ошибке вы узнаете от линковщика. Информативностью его сообщения не отличаются

В приведённом примере unresolved external symbol «public: int __thiscall A::f(void)» всё предельно понятно. Если, конечно, понимать в принципе что такое линковка и что такое в данном контексте «символ». А для этого стоит почитать если не целую книжку, то хотя бы главу о линковке из какого-нибудь учебника плюсов — например, Стивен Прата «C++ Primer Plus».

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

Ни в коем случае это не должно быть ошибкой. Ибо философия плюсов: хочешь стрелять себе в ногу — стреляй. Тем более, что иногда отсутствие виртуального деструктора в подобном классе оправдано (в таком случае хорошей практикой будет сделать его protected, чтобы не пришлось ловить ошибки в рантайме).
А вот ворнинг подобный вполне себе можно сделать. И во многих компиляторах он есть. В той же вижуал студии он доступен на третьем уровне предупреждений: C4265 — class has virtual functions, but destructor is not virtual. Компилить-то вообще желательно с /W4. Или пройтись по списку ворнингов на сайте майкрософта и повключать точечно те, которые будут полезны конкретно на вашем проекте.

Перегрузка методов

Пример с добавлением в производный класс функции с таким же именем, как виртуальная функция в базовом, заставляет задумываться, всё ли у вас в порядке с архитектурой. Даже если б оно работало без необходимости писать using, это выглядит как расстановка граблей для других программистов (или для себя же в будущем). Я бы новую функцию по возможности называл по-другому, вместо того, чтобы пытаться сделать её именно перегрузкой.
Включенный в язык механизм hiding’а унаследованных мемберов с таким же именем как раз призван застраховать девелопера от риска неявно вызвать не ту функцию, которую он хотел. А если уж хочешь иметь доступными обе — будь добр, напиши явно using. Это правильная вещь, на мой взгляд. Примерно как если бы конструкторы были explicit по умолчанию.

К сожалению, никто не подскажет, если после вызова конструктора у вас все же останутся неинициализированные поля.

Компилятор подскажет, если в нём реализован и включен такой ворнинг (-Weffc++ в GCC). Анализаторы вроде cppcheck или valgrind тоже такие проблемы ловят.

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

Про списки инициализации: это придумали для того, чтобы отличать инициализацию (вызов конструктора) от присваивания. Ибо синтаксис «member = init;», вызывающий конструктор вместо оператора=, выглядел бы куда более странно, нежели чёткое разделение этих принципиально разных вещей с помощью списков инициализации.

Опытные разработчики скажут, что здесь лучше код переписать так:
auto p = shared_ptr(new SomeObj);

Опытный разработчик такое посоветовать может только в одном случае: когда ожидается, что на объект будут существовать weak поинтеры, которые переживут лайфтайм самого объекта. Довольно редкая ситуация, на моей практике.
Да, дальше было про make_shared, но от заявления «опытные разработчики скажут писать вот так» я изначально слегка прифигел :)

поделиться со мной и другими читателями ссылками на материалы, которые научат правильному использованию современного C++, особенно в контексте Unreal

Про контекст анриала не знаю, а вот по чистым плюсам совет прост: Скотт Майерс, серия «Effective C++», а также его многочисленные презентации на ютубе. Это покрывает почти все подводные камни C++, подобные тем, что описаны в статье.

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

В том и проблема: C++ адекватный язык для тех, кто на нём пишет постоянно. Для тех, кому надо время от времени на плюсах что-то пописать, а затем вернуться к другому (основному) стеку, это ужасный язык. Потому что нужно помнить много вещей, которые вылетают из головы, если долго на нём не писать.

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

Здесь особо нечего осмысливать. «Исторически сложилось», наследие C, ряд сомнительных или явно неудачных решений в дизайне самого C++ ...

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

В конце не хватает совета использовать по возможности для инициализации объектов классов фигурные скобки вместо квадратных («MyObject object{init};»), а для кастов между типами — static_cast вместо сишных вариантов.

Вы уже второй, кто это пишет — взял на вооружение, спасибо.

А для этого стоит почитать если не целую книжку, то хотя бы главу о линковке из какого-нибудь учебника плюсов — например, Стивен Прата «C++ Primer Plus

Отличная книга, прочел от корки о корки лет 5-6 назад.Знакомым тоже рекомендую.

Ни в коем случае это не должно быть ошибкой. Ибо философия плюсов: хочешь стрелять себе в ногу — стреляй. Тем более, что иногда отсутствие виртуального деструктора в подобном классе оправдано (в таком случае хорошей практикой будет сделать его protected, чтобы не пришлось ловить ошибки в рантайме).
А вот ворнинг подобный вполне себе можно сделать. И во многих компиляторах он есть. В той же вижуал студии он доступен на третьем уровне предупреждений: C4265 — class has virtual functions, but destructor is not virtual. Компилить-то вообще желательно с /W4. Или пройтись по списку ворнингов на сайте майкрософта и повключать точечно те, которые будут полезны конкретно на вашем проекте.

Здесь вы солидарны с Сергеем dou.ua/...​_campaign=comment#1852928
Повторюсь, что не знал о таких ворнингах, и я из тех, кто по коду любит перестраховаться, так что теперь буду использовать, спасибо.

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

Так меня возмущает то, что это касается и override функций — имхо перебдели. А простой overload в классах наследника — вполне допускаю, что такое поведение оправдано, хоть и не видел подобного в других языках. Тем более, что в 99% случаев перегружаю только конструкторы.

Компилятор подскажет, если в нём реализован и включен такой ворнинг (-Weffc++ в GCC). Анализаторы вроде cppcheck или valgrind тоже такие проблемы ловят.

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

Да, дальше было про make_shared, но от заявления «опытные разработчики скажут писать вот так» я изначально слегка прифигел :)
Про контекст анриала не знаю, а вот по чистым плюсам совет прост: Скотт Майерс, серия «Effective C++», а также его многочисленные презентации на ютубе. Это покрывает почти все подводные камни C++, подобные тем, что описаны в статье.

О make_shared узнал от рецензента, который прислал ссылку на более новое издание Мейерса.
Более того именно с чтения его книги родилась эта статья 2 месяца назад. Сначала выписывал мисли для себя, потом поделился с коллегами и они сказали, что им бы было интересно почитать подобное в виде статьи.
Спасибо за комментарий

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

Чуть недопонял. Примеры чего?

Отличная книга, прочел от корки о корки лет 5-6 назад.Знакомым тоже рекомендую.

В целом да, хорошая. Помогла мне в своё время разобраться с iostream’ами (наиболее монструозная и неудачно задизайненная часть стандартной библиотеки C++, на мой взгляд). Очень подробно всё расписано.
Помню только в ней одну важную ошибку: в разделе про placement new они приводили пример с конструированием объекта на невыровненном буфере char’ов. В общем случае это будет бо-бо.
Буфер, на котором конструкируется объект, обязан быть выровнен на alignof(Object) байтов. Для этого стоит использовать alignas. Или компиляторозависимые альтернативы, если по какой-то причине C++11 не доступен.

Так меня возмущает то, что это касается и override функций — имхо перебдели.

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

Подозреваю мало кто из студентов о них знает тоже .

Именно из студентов — думаю, так и есть. Но разработчики, которые пишут на плюсах профессионально, о подобных инструментах как правило знают.

Чуть недопонял. Примеры чего?

Примеры статей, где для вполне опытных разработчиков на C#, Java и других популярных языков, но новичков в С++, были бы хорошо подобраны основные подводные камни и проблемы, с которыми придется столкнуться буквально с самого начала и как их решать?

В целом да, хорошая. Помогла мне в своё время разобраться с iostream’ами (наиболее монструозная и неудачно задизайненная часть стандартной библиотеки C++, на мой взгляд). Очень подробно всё расписано.

Я правда ее перелистывал пару месяцев назад — уже не так впечатляет. Да, много чего из нее я почерпнул полезного в свое время, но С++11 так описан достаточно куцо, а более новых возможностей нет вообще, так как вышла она уже скоро как 10 лет и переиздания не было.

Я независимо от оверрайдов стараюсь избегать такой мешанины из функций с одинаковым именем на разных уровнях иерархии.

Конечно же «що занадто, то нездраво», но полиморфизм это один из столпов ООП и виртуальные методы позволяют решать множество задач, например элегантно описывать пакетную обработку объектов унаследованных от одного класса (в C#|Java и имплементирующего один и тот же интерфейс). Потому переопределение методов в мире ООП языков оправданно используются достаточно активно.

Именно из студентов — думаю, так и есть. Но разработчики, которые пишут на плюсах профессионально, о подобных инструментах как правило знают.

Обратите внимание, что у статьи изменился заголовок — dou.ua/...​fficult-to-learn/#1853645

Примеры статей, где для вполне опытных разработчиков на C#, Java и других популярных языков, но новичков в С++, были бы хорошо подобраны основные подводные камни и проблемы, с которыми придется столкнуться буквально с самого начала и как их решать?

Таких с ходу не вспомню.

Конечно же «що занадто, то нездраво», но полиморфизм это один из столпов ООП и виртуальные методы позволяют решать множество задач, например элегантно описывать пакетную обработку объектов унаследованных от одного класса (в C#|Java и имплементирующего один и тот же интерфейс). Потому переопределение методов в мире ООП языков оправданно используются достаточно активно.

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

Я независимо от оверрайдов стараюсь избегать такой мешанины из функций с одинаковым именем на разных уровнях иерархии.

Мне показалось, что это было и о перепреоделения тоже. А так мы пришли к консенсусу

А простой overload в классах наследника — вполне допускаю, что такое поведение оправдано, хоть и не видел подобного в других языках. Тем более, что в 99% случаев перегружаю только конструкторы.

Набросили знатно, да. Годная пятничная тема. Но кроме этого, зачем вы это все написали, я так и не понял. Пишите дальше на C# и берегите нервы.

Это извечное желание внутреннего роста — ничего не могу с ним поделать ;).
Работа работой, но хочется и что-то для души делать после нее.

Да этот язык делает больно по другому — он стабильно мне напоминает что я нихрена не понимаю в нем на самом деле ))

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

Надеюсь в вашем ответе кроме сарказма присутствовало и немного искренности ;)

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

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

эту работу выполняет линкер

Подстановку минимума информации для компилятора из других файлов (заголовков) выполняет препроцессор

Все кто хочет что то исправить из вполне обоснованных недостатков. Заходите stdcpp.ru и сабмдите сови предложения, группой рулят ребята из Яндекса (их самих хотят национализировать и т.д.) — дельные идеи и предложения летят в стандарт. 2. Да, если кто еще не в курсе С++ 20 — принят и там есть модули www.youtube.com/watch?v=AvPiGstxV_g

stdcpp.ru

Чудова порада. А немає якоїсь активності на stdcpp.su чи стдцпп.рф або в gov.ru так, щоб не Яндекс рулив, а відділ Е чи К ФСБ РФ?

У вас як домен ru так одразу — фсб і інщі терроритстичні організаціі, під прицілом кремля, на тому сайті взагалі жодної політики (а самі хлопці з яндекс таксі, пропагандують проти убера непевно). За такою логікою будь який Іракський сайт створив особисто садам хусейн, включно з «fuckhusein.iq». 2. Не влаштовує Яндекс, давайте створимо власну робочу группу, й приєднймося до комітету з стандартізації С++.

Я не знаю, що там з Іраком, Яндекс — російська корпорація підконтрольна російським спецслужбам з офісами в окупованому Криму із золотою акцією якою володіє російський державний банк. Взагалі, ходити в англосвіт через російське віконце — ну, трясця, не в 21-м же столітті.

Зараз у нас взагалі нічого нема, ми жодним чином не представлені. Україньскої робочої группи нема, бібліотек в Boost теж нема, пошувика україньского — нема, найбільщі IT компанії — з дебільшого фліали. (віконце і поверх добрі, але будівля смердить і то правда)

А естонська робоча група є? Грузинська? Угорська? Польська? Пошуковики? Бібліотеки в Boost’і?

Створи українську, і коміттьте в стандарт більше, ніж російська. Може, ще хто з українців новий STL напише.

Одразу після створення українського UNIX та українського C займуся українським STL, обов’язково.

Здається, я зрозумів, на що ви натякаєте, хоча мені трішки й дивно, що ви людину, що в 1977 році у віці 27 років вирвалася з совдепії у штати й в 1993 році працюючи в HP створила STL чомусь називаєте не євреєм і не американцем, а росіянином. Втім, навіть якби й росіянин — що з того?

Те, що, наразі, російські програмісти та народжені в Росії сильно вплинули на ІТ (гугл, нджінс, СТЛ). Українці — здається, ні. Можливо — нажаль. Якщо хочемо це змінити — треба робити щось велике й потрібне, на зразок того ж СТЛ, нджінкс чи гугла. Якщо не хочемо робити — тоді треба жити з розумінням того, що російськомовні сайти мають вплив на ІТ, а україномовні — не мають. Бо не зробили в нього вагомий внесок.

Українці також дещо вплинули, проте ми не імперська нація й маємо менше можливостей й мотивації привласнювати чуже, от як наприклад єврея Бріна, батьки якого виїхали в ті ж роки, що й Степанов, й чиє коріння, як і коріння більшості радянських євреїв, сягає України й Білорусі. Це слід розуміти й припинити тягнути до нас оцю російщину.

Тоді хтось (СБУ?) має відредагувати англійську вікіпедію, щоб увесь світ знав, що Брін — український американець, а не російський. Інакше — Росія буде вважатись сучасною країною, котра щось може, й впливає на світове ІТ, а Україні — аграрною країною, котра лише просити грошей вміє, бо постраждала від російської агресії. І ставлення буде відповідне — як до бомжа. Поки Україна не покаже, що на щось спроможня. А так — кожен бомж любить розповісти, як в армії служив в спецназі й охороняв Хрущова в Афганістані.

Мммм... вам якось треба коректніше сформулювати завдання для СБУ, бо поки що не зрозуміло, що саме там редагувати:

Sergey Mikhaylovich Brin (Russian: Серге́й Миха́йлович Брин; born August 21, 1973) is an American software engineer and Internet entrepreneur

Brin was born on August 21, 1973, in Moscow in the Soviet Union,[6] to Jewish parents,[7] Eugenia and Mikhail Brin

Оце треба додати в біографію, очевидно:

чиє коріння, як і коріння більшості радянських євреїв, сягає України й Білорусі. Це слід розуміти й припинити тягнути до нас оцю російщину.

Є така штука — PR. Ним хтось має займатись. За запитом russian programmer гугл видає оце en.wikipedia.org/...​_of_Russian_IT_developers За запитом ukrainian programmer — лінки на те, як знайти аутсорс. Складається враження, що російські програмісти — видатні люди, а українські — здатні лише бути в рабстві на галерах.

І це — те, як країна виглядає в світовій спільноті. Тому не варто дивуватись і наявності російських груп розробників, і повазі світу до них. PR провал України — ніхто не знає про наших геніїв.

Як для колишнього вікіпедиста ці розмови для мене надзвичайно нудотні. Є бажання — візьми й зроби, Хтось (вікіпедійний персонаж-мем) цим займатись не буде. Можна констатувати факт, що росіяни, в яких їх вікіпедійна група фінансується державою і яких суто демографічно в кілька разів більше завжди будуть продукувати більше піяру ніж українці. Можна, але який в тому сенс? Нам своє робить.

Нагадаю, цей тред почався з моєї пропозиції не ходити в світове IT через Москву.

я пробував якось щось дописати у вітчизняномовну вікіпєдію суто технічне але там одразу же ж набігло якоїсь ацької школоти що я сильно пріфігєл і покинув вже назавжди

гірше за дурня лише дурень ініціативний (к) (тм) #немоё

наразі, російські програмісти та народжені в Росії сильно вплинули на ІТ (гугл, нджінс, СТЛ). Українці — здається, ні.

ну здрасьте
ru.wikipedia.org/wiki/KeyRus
Григорович (GSC)
и т. д.

P.S. я думаю, что и Михаил Израилевич Брин и его сын Сергей, очень бы сильно поспорили о вкладе, как русских так и России, в создание google

Всі ми і вони — вихідці з Африки. Так що африканці вплинули.

На cppcon чи то 2016, чи 2107 Степанов розказував як він запропонував ідею STL Страуструпу, тому сподобалося і він почав писати код. Але оскільки він C++ не знав і вчити не хотів то по кілька разів на тиждень смикав Б’ярна різними питаннями. І в кінці сказав, що не вважає С++ хорошою мовою, але конкретно STL це єдине, що робить її юзабельною.

Тьфу блін, Степанов, не Александреску. Виправив.

але конкретно STL це єдине, що робить її юзабельною

Іронічно, враховуючи, що дизайн у STL доволі специфічний і незручний у використанні. Звикнути можна, але коду доводиться писати більше, ніж хотілося б. Яскравий приклад: «remove-erase ідіома».

не совсем буст, но поляки написали Mockito которым пользуется пожалуй каждый джавист

Не важно что, где и как, если попадается слово ru/рф — сразу на кол. С таким подходом вас можно тоже на кол садить. Если политику приплетать в общество open source — то это будет явно очередной случай удаления пачки репозиториев, чисто из-за идиотов и их срачей. Не выставляйте себя как минимум глупцом.

Я в курсе концепции модулей в с++20 — там еще много чего вкусного.
Только массовыми их использование станет лишь со временем и новичкам еще очень долго светит работать по старинке.

Только вот нормально на stdcpp.ru не зайти, надо через tor, прокси и т.п.

Время полной компиляции проекта на моем компьютере занимает 10 секунд. Берем пустой проект на Unreal, добавляем единственный объект с С++ классом. Вносим туда малейшее изменение — время компиляций и линковки до запуска, минимум 7 секунд на том же i7-8700.

Taк это же супер! Плюсы на целых 3 секунды компилятся быстрее, чем до-диез! ;-)

:)
Вы конечно же «случайно» выделили не полную цитату ;)

Сейчас я работаю над проектом на C# под Unity, который состоит из 4300 файлов с кодом. Это 25 мегабайт исходников! Время полной компиляции проекта на моем компьютере занимает 10 секунд. Берем пустой проект на Unreal, добавляем единственный объект с С++ классом. Вносим туда малейшее изменение — время компиляций и линковки до запуска, минимум 7 секунд на том же i7-8700.

Вы еще анекдоты начните объяснять... :) Это ж сарказмЪ был.

Ну я как бы со смайликами отвечал ;).

C# под Unity, который состоит из 4300 файлов с кодом. Это 25 мегабайт исходников! Время полной компиляции проекта на моем компьютере занимает 10 секунд

он их даже распарсить за это время не успеет
скорее всего инкрементная компиляция или кэширование

Записать видео как я нажимаю Rebuild project?
У меня 12 потоковый процессор и проект лежит на NVMe диске.

Ну запишите, только после git clean -ffdx. А по хорошему, сюда бы еще добавить время на ngen, иначе результат не совсем равноценный.

Із С++ порівняти не можу, немає якого великого проекту під рукою, але порівняно із C C# не виглядає таким вже крутим в компіляції.

25Мб, навіть якщо це чистий код без ресурсних файлів, це десь 400.000 строк коду. Компілювалось воно у тебе 10 секунд, тобто 40.000 строк в секунду.

Беремо звичайне ядро Linux, там 25.000.000 строк в 50.000 файлов. i7 старенький 8-потоковий, звичайний SSD. Час повної зборки з нуля — 6 хвилин, 360 секунд. Тож маємо швидкість 70.000 строк на секунду, в півтора раза швидше за твій C# :)

Це якщо підходити формально.
Насправді, я думаю, що швидкість дуже залежить від самого компілятора, від його налаштувань і головне — від build-системи. Чистий make, cmake, VisualStudio, AndroidStudio, якісь Xcode-и та Swift-и, etc — всі мають свої ніши й саме в них показують найкращі результати.

Порівнювати ж збирання майже пустого проекту із проектом середнього розміру мені здається трохи маніпулятивним.

Так я ж порівняв порожній проект на С++ для Unreal з досить великим проектом на C# для Unity і час виявився однаковим. Порівняння явно не на користь С++. Про які маніпуляції йде мова?

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

Не сказал бы, что С++ такой уж прям сложный и недружелюбный. Если в правильном порядке всё изучать, то окажется уже не так уж и сложно. А то у многих фобия к С++ начинается с универов. Где учат в стиле:
Ой, вы не знаете как работает память, что такое хип и стэк? Ну держите хренову тучу задач на указатели
Или же
А сделайте вот эту задачу, но STL юзать нельзя, мы же еще не проходили темплейты!1

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

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

P.S Ну в среднем да, сложнее многих других.

не сложнее )) просто «другие» точно так же ж «хорошо разбираются» и в «других» языках как и в «плюсах» )) селяви

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

Вы очень точно описали, как у нас учат плюсам в ВУЗах.
Вот еще в институте, то есть 20 лет назад, я без проблем на Borland C++ писал любые лабы или курсачи, которые требовались в политехе. А потом начал читать умные книжки и понял, что от плюсов там было только решение задачи в виде класса. А остальное в стиле Си.

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

меня учили С в стиле С ))) но к кодингу я остыл не поэтому )

По поводу «не особо сложнее» и «не недружелюбный».
Так можно говорить в двух случаях:
— вы на плюсах так давно пишете, что уже привыкли ко всем его сложностям, неоднозначностям и неудобствам
— вам КАЖЕТСЯ, что вы уже хорошо поняли плюсы

В среде первых тем не менее существуют Брайт, Мейерс, Александреску и другие — которые считают этот язык чрезмерно сложным и что-то реально делают для того, чтобы у программистов были альтернативы или хотябы описывают то множество подводных камней, которые ожидают разработчиков на С++.

Основное большинство вторых просто мало на нем писали.

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

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

Люто плюсую про универы. В моём случае так и было. Почти всех одногруппников после подобного курса «программирования» на «плюсах» тупо тошнило от этого языка. Хотя настоящих плюсов в рамках этого курса они, по сути, и не видели.

ссылками на материалы, которые научат правильному использованию современного C++
в клуб избранных, куда добираются единицы

... ну панислась! ))

C++ дайджест #27: Continuous Integration

C++ дайджест #26: StayAtHome та вивчай Machine Learning

C++ дайджест #25: засідання комітету зі стандартизації С++ 20, online-компiлятори та задачі для практики

C++ дайджест #24: Code cleanup, VR, з чого почати вивчення С++ та створюємо валентинку

C++ дайджест #23: оптимізація компіляції та підсумки року

C++ дайджест #22: детально про оптимізацію, Trip Report засідання комітету зі стандартизації

C++ дайджест #21: дебаг у Visual Studio та Visual Studio Code

C++ дайджест #20: CppCon 2019, Open Sourcing STL від MSVC

C++ дайджест #19: підготовка до співбесід

C++ дайджест #18: Summer ISO C++ standards meeting, technical vision for Qt 6

C++ дайджест #17: Embedded програмування на Raspberry Pi, Embedded Linux розробка

C++ дайджест #16: embedded з Arduino

C++ дайджест #15: геолокацiя з Qt, ACCU 2019

C++ дайджест #14: Graphics API — OpenGL, DirectX, Vulkan, Metal

C++ дайджест #13: OpenCV, результати зимового cpp meetup

C++ дайджест #12: CUDA, нові фічі Visual studio 2019

C++ дайджест #11: підсумки року, реліз Visual studio 2019

C++ дайджест #10: результати зустрічі ISO C++ Committee

C++ дайджест #9: як працює Clang-Tidy, матеріали з CppCon 2018

C++ дайджест #8: Qt та Unit tests

C++ дайджест #7: оновимо знання з Qt

C++ дайджест #6: огляд менеджерів пакетів

C++ дайджест #5: огляд С++ 17 та 20, реліз Visual Studio 15.8 Preview 3

C++ дайджест № 4: новинки стандарту і compile-time трюки

C++ дайджест № 3: Управління пам’яттю та алгоритми

C++ дайджест #2: Парад IDE

C++ дайджест #1: реліз Visual Studio 2015, нова платформа WebAssembly

Певні недоліки C++ виходять з самої сутності цієї мови програмування і є частиною його природи так само як частиною природи Haskell є геморой з імперативними розрахунками. І дивно й неправильно порівнювати мову з суворою типізацією, шаблонами та «не платимо за те, що не використовуємо» із класово іншими Java та C#.

З іншого боку, частина перерахованих «недоліків» (як-от #pragma once чи особливості використання кваліфікаторів доступу) це насправді взагалі ні про що, косметика, а порівняння сумісності й версій мови програмування, що старша за мене, компілятори якої від десятків проектів існують під сотні платформ з C#, який створювався як робота над помилками Java і чий трансльований код існує в обмеженому середовищі контрольованому однією корпорацією, ну це взагалі смішно.

В цілому ж можу хіба порадити читати книжки, зокрема Дизайн та еволюцію C++.

Неочікував, що в коментарях побачу когось з таким самим прізвищем, як у мене :).
Артеме,

І дивно й неправильно порівнювати мову з суворою типізацією, шаблонами та «не платимо за те, що не використовуємо» із класово іншими Java та C#.

Класово інші Java|C# так само мають строгу типізацію та шаблони, нехай не такі потужні, як шаблони С++.
В той же час Александреску вважає, що D з шаблонами вправляється ще краще. Ця мова також претендує на звання «мови для системного програмування» і немає дуже великої кількості проблем, які від самого початку переслідують С++.

Звісно в межах невеликої статті надзвичайно складно повністю розкрити таку непросту тему, тому я просто перелічив деякі проблеми з якими перетинаются новачки вже від самого початку знайомства з С++. Ці проблеми перелічені в порядку від найпростіших до більш екзотичних, які для мене свого часу стали одкровенням. Тож звісно для вас, як людини, яка вже не перший день пише на С++, більшість з них косметика. А для новачків це необхідність вчити в рази більше правил та виключень, ніж в інших сучасних мовах.

Мені здається, що С++ можна зробити більш дружньою до програмістів, наприклад додавши автоматичну перевірку віртуальності деструктора, коли компілюється клас, який від нього наслідується. Переконаний, ви самі зможете привести кілька прикладів автоматизації пошуку помилок, які зараз виконуються за допомогою синтаксичних аналізаторів і тп і які було б непогано отримувати з коробки.

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

Прикладів гарних мов вистачає. Тим не менш доводиться раз по раз повертатися до С++, так як в геймдеві їх прижилося не так і багато.
З книжок я читав Шилда, Еккеля, Прата та Меєрса.

Класово інші Java/C# так само мають строгу типізацію та шаблони, нехай не такі потужні, як шаблони С++. В той же час Александреску вважає, що D з шаблонами вправляється ще краще

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

наприклад додавши автоматичну перевірку віртуальності деструктора, коли компілюється клас, який від нього наслідується

В цьому немає потреби, бо якщо ми не видаляємо підкласи через базу зайва віртуальність нам не потрібна: не використовуєш — не платиш. Натомість якщо видаляти через базу то, наскільки я пам’ятаю, є якісь ворнінги.

З книжок я читав Шилда, Еккеля, Прата та Меєрса.

От я дуже раджу саме Дизайн на еволюцію, бо завдяки ній стає зрозумілим чому C++ саме такий, який він є, й багато незрозумілостей отримують логічне пояснення.

Перші дві мають не зовсім ті шаблони ну, принаймні, коли я в останнє на них дивився.

Їх шаблони і зараз далекі від ідеалу, тим не менш вони дозволяють легко вирішувати дуже велике коло проблем причому, при використанні constrains, код виявляється абсолютно типобезпечним і зручним у читанні та використанні. Так, незалежно від конкретного типу, IDE одразу показує помилки в коді, чого немає у випадку використання шаблонів С++. В статті я привів приклади незручності роботи.

В цьому немає потреби, бо якщо ми не видаляємо підкласи через базу зайва віртуальність нам не потрібна: не використовуєш — не платиш. Натомість якщо видаляти через базу то, наскільки я пам’ятаю, є якісь ворнінги.

В статті я привів приклад, в якому немає ніяких ворнінгів, хоча код працює некоректно.

За пораду почитати «Дизайн та еволюцію C++.» дякую, обов’язково гляну.

наприклад додавши автоматичну перевірку віртуальності деструктора, коли компілюється клас, який від нього наслідується.
struct A
{
public:
	~A(){}
};

struct B : A
{
	~B() override {} <= error C3668: 'B::~B': method with override specifier 'override' did not override any base class methods
};

... ну и конечно классическая занимательность с чего ты вообще решил что в плюсах деструкторы _должны_ быть виртуальными «автоматически» и скажем почему они таки _должны_ быть таковыми в парадигме C# ? ))

Мені здається, що С++ можна зробити більш дружньою до програмістів

build a system that even a fool can use and only a fool will want to use it
/ Shaw’s Principle of Murphy’s Law /

У вас явное предубеждение против автора.
Я вам задал несколько вопросов, которые могли бы помочь понять причину этого, но вы на них не ответили.
Как рецензент вы должны были увидеть, почему ваш пример с override неуместен, ибо в статье это подробно разобрано.
Но так как вы внимательно ничего не читаете и на неудобные вопросы не отвечаете, то не вижу смысла комментировать ваши наезды.
Научитесь вести диалог культурно, потом приходите.

поступил запрос «показать как надо» и на этом простом примере я математически покажу почему так и почему и дальше только так и будет

читай «почему они тупые и почему с этим уже никогда нельзя будет ничего сделать но приходится просто с этим жить»

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

вся история началась в конце позапрошлого века когда массовый рост промышленности случившийся после промышленной революции потребовал столь же ж массового если не больше привлечения человеческого ресурса чисто технически рабочей силы и оказалось что тут всё не просто и вопрос здесь именно в массовости что то же ж самое что уже было объявлено только уже «в промышленных масштабах»

промышленникам понадобились рабочие и взять их было больше не откуда кроме как фигурально «с улицы» причём ещё даже городов потому как города в своей массе появятся только позже так по разным оценкам на начало века соотв. позапрошлого городское население составляло до 20% общего в то время как на момент становления уже капиталистической промышленности как таковой где-то 1920-го года ситуация меняется ровно наоборот и городское население составляет уже 80% общего речь здесь конечно об капиталистических промышленных развитых странах

проблема «взять с улицы» даже чисто городской состояла конкретно в сортировке «годен не годен» и если в армию можно брать просто по внешнему виду примерно оценив здоровый вид и достаточный рост как «здоров» то с заводами всё оказалось несколько сложнее просто потому как впервые в истории человечества человечеству понадобились таки мозги именно в массовом количестве «в промышленных масштабах»

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

и тут оказалось снова всё не просто просто потому что если бы б были у человеков дипломы скажем средней школы и профтехучилища то человеков можно было бы б просто принимать на завод по факту оценки «хорошо» но дипломов не было средних школ не было профтехучилищ не было образования как такового не было точнее же ж оно только зарождалось как необходимость чисто техническая с точки зрения всё той же ж массовой промышленности но тут вопрос что это уже не дети и надо взять «какое-то население» и быстро его обучить и вопрос тут в том а как определить при этом «годен не годен» ещё до того как брать в обучение просто потому что напомню речь идёт об капитализме и с тупыми дебилами

здесь «дебил» как наиболее лёгкая форма олигофрении что в свою очередь означает «умственная отсталость» если есть вопрос оставаться «политкорректным» для всё того же ж массового населения

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

представляю вам систему «коэффициента интеллекта» более известную как iq что есть банальной аббревиатурой от английского intelligence quotient

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

Intelligence quotient

сама по себе история изобретения и устройство теста конечно же ж по своему занимательны но в данном контексте суть не в этом но более конкретно в той кривой что была поставлена в основу самой системы iq и определяла чисто статистическое распределение среди населения и эту кривую вы можете найти на иллюстрации ко всё той же ж англоязычной статье по предмету как более иллюстративной и чисто технически достоверной

Normalized IQ distribution with mean 100 and standard deviation 15

... если взять лирическое отступление то на этой же ж кривой вы найдёте и дебилов которые находятся в той части где iq ниже 70 и соотв. эта теория прекрасно покрывает и их как чисто статистическую часть населения и соотв. даже несмотря на то что с точки зрения современной политкорректности слово является ругательным чисто технически с научной точки зрения оно является обычным научным фактом более чем достоверным цитируя крайне уместную аналогию «жопа есть а слова нет» (к) (тм)

одной из ключевых идей новой повестки дня после промышленной революции и соотв. эпохи массовой промышленности стал вопрос «новой нормальности» и как именно она определялась я уже написал но повторюсь кратко «рабочий должен быть способен не ломать машину и не калечиться сам» и одной из идей «методики сортировки человеков» было то чтобы кривая распределения «нормальности» покрывала своей «нормальностью» по крайней мере 70% общего населения что вы и можете наблюдать на приведенной иллюстрации и при этом общее покрытие «не идиотов» составляет вообще 95% и здесь конечно существуют разные гипотезы и чисто практические моменты которые учитывают и рассматривают случаи где не везде так но в общем смысле важен сам принцип распределения

ещё раз повторюсь «новая нормальность» определяет требования к простому рабочему которые определяются совсем просто «не ломать машину и не калечиться сам» ничего другого слишком принципиально от простого рабочего не требуется и всему остальному уже можно научить

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

даже на этой кривой вы видите что от общего населения этих «умников всяких» аж целых 16% ну в самом оптимистичном пределе ну так сложилось такая вот человеческая природа данная непосредственно сегодня в доступных на сегодня чисто биологически человеческих мозгах

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

но на самом деле если вы всего этого не понимаете это не страшно просто потому что...

следи за рукой!

... просто потому что это _нормально_

здесь смотрите какая интересная история получается фишка здесь в том что сам термин «умственная отсталость» рассматривается нами обычными людьми только с точки зрения нас самих обычных людей читай «нормальных» но как только мы сдвинем точку отсчёта «нормальности» от тех «нормальных» какими их задумали капиталисты эксплуататоры рабочего класса до тех же ж iq 115 какими они же ж сами и нарисовали эту кривую реального распределения реального населения как тут же ж за пределами «нормальности» окажутся 70% реального населения это не считая уже совсем «отсталых» даже с точки зрения самого основного населения

читай как только мы становимся «не нормальными» но принимаем это за новую меру и точку отсчёта так сразу «умственно отсталыми» стают реально 70% реального населения селяви

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

дальше уже простая математика берём общую оценку населения 37 млн штук берём от неё процент программистов по оценкам 160-200 тыс штук и получаем 0.43-0.54% от всего населения но на самом деле конечно от всего населения считать не правильно потому что это вообще всех включая младенцев и депутатов и Самого Отца Нации а работоспособного населения по оценкам уже 18 млн штук и соотв. процент программистов уже ровно в 2 раза больше 0.86-1.08%

этого конечно совсем мало ведь «умников» согласно кривой аж 13-14% но не забывайте что современные технологии это не только программисты это ещё и врачи и техники и даже просто разного рода руководители как реально способные не только «не ломать машину и не калечиться сами» но реально организовать работу и взаимодействие хотя бы б 2-х машин и 3-х рабочих и поверьте «просто рабочие» на это таки не способны впрочем опять же ж вы легко это можете проверить

как следствие «сообщество» программистов в Родине растёт и как чисто техническая чисто статистическая необходимость и необратимость «расширение базы» приводит ко всё большей «диффузии распределения умственной отсталости» которая опять же ж напомню не то чтобы дебилы в общем медицинском смысле но «умственно отсталые» считая от другой точки отсчёта кривой распределения среди всего населения и как следствие в т.ч. уровень «статей» чисто технический также подвержен такой «диффузии чисто технического уровня» чтобы убедиться в этом опять же ж многого не надо а достаточно посмотреть и найти достаточное количество «статей» уровня «как установить ide и сделать hello world» желающие убедиться в реальности кривой распределения опять же ж могут на практике «вырвать» человека из своего «нормального» окружения и поставить ему такую простую задачу в т.ч. прямо по готовой инструкции

а потом спросить объяснить как он понимает что именно он делал и что именно происходило на каждом из этапов этой инструкции ))

и это только начало потому что потом на этой основе появляется ещё и тот самый популярный сегодня вопрос уже «эмоционального интеллекта» ))

Чесно старався все прочитати і зрозуміти, але не певен, що зробив це успішно.

Взагалі якщо мова йде про зниження середнього інетелеку то це так і є і обумовлено антропологічно та біологічно. З того моменту як інтелект перестав буту суттєвим фактором для виживання і педерачі генів він почав падати. За оцінками за останні 20 тисяч років обсяг головного мозку знизився на 5% в середньому і нема ніяких причин щоб він не знижувався і далі.

І хоча обсяг мозку прямо з інтелектом не пов’язаний (найбільші в середьному мізки у монголів та казахів здається), він тим не менш важить.

Останній період в історії сапіенсів коли більш інтелектуальні давали більше нащадків які виживали — це були часи кроманьйонців.

Зараз менш інтелектуальні індивіди як правило дають більше нащадків тим самим закріплюючи тенденцію на зниження середьного інтелекту. Якщо кроманьйонцю у віці 8-10 років треба було знати абсолютно все для виживання: як полювати, як робити сховище на ніч, що їсти, як зберігіти здобич, як лікувати вавки і хвороби, то зараз у нас дитинство продовжується аж до після 20 років. І при цьому розмноження тих хто має показники нижче середніх нічого не стримує — вони не гинуть від голоду, холоду, хижаків чи ідіотських вчинків.

Так що так і буде — крихітний відсоток буде усе рухати вперед, трохи більша частка буде підтримувати усі інфраструктури щоб ті функціонували, а решта буде «про запас» (на випадок катастрофи щоб швидко відновити популяцію) їсти чіпси на дивані перед телевізором.

Чекаю на статтю «Почему язык ассемблера такой недружелюбный к программистам»
Бо дійсно, що це за два альтернативних синтаксиса — Intel і AT&T? Де стандарт? Що це за розширення на розширенні, я не встиг вивчити x87 а тут MMX, тільки взявся — одна за одною нові версії SSE. Я тільки до SSE2 довчив а вже час вчити AVX, і тих вже кілька.
ООП не підтримується, метапрограмування немає, є тільки архаїчні макроси. І взагалі, хіба автори асемблера не чули про сучасни тренди у мовах програмування — ось в Rust є ownership rules, а в Хаскелі чистота. Як написати на асемблері чисту функцію? І чому забули про «скомпілилося — працює»?

ООП не підтримується,

Borland Turbo Assembler вам в помощь)

Як написати на асемблері чисту функцію?

RET ? ))

Як написати на асемблері чисту функцію?

а вот зацени исчо ))

MOV EAX EIP
RET

У нас в політехнічному, здається на 2 курсі, був курсач на Turbo Assembler.Треба було написати програму з графічним інтерфейсом яка на динамічних списках в кілька рівнів реалізовувала роботу з досить складною структурою даних. Свого часу мені сподобалась ця задача.
І звісно в мене немає ніякого бажання порівнювати Assembler з високорівневими мовами програмування ;).
В геймдеві зараз найпоширеніші C#, Python та C++ і я був би тільки радий, щоб високорівневих мов тут було більше. На шарпі я пишу останні 4 роки, на Python 5 років писав у Wargaming, зараз мені хочется чогось більш продуктивного, тож і доводиться знову повертатися до С++.

В мережі безліч матеріалів на тему як правильно використовувати С++. Я особисто з задоволенням читаю www.fluentcpp.com та www.reddit.com/r/cpp
Також, якщо є час, можна подивитись доповіді з конференцій. Ось ця мені найбільше сподобалась з минулорічного CppCon:
www.youtube.com/watch?v=FJJTYQYB1JQ
Ще можна подивитись C++ Weekly Джейсона Тернера:
www.youtube.com/...​/UCxHAlbZQNFU2LgEtiqd2Maw
Ну і на godbolt.org можна поекспериментувати з різними компіляторами і подивитись на асемблер, який вони генерують, щоб краще розуміти що під капотом.

Між іншим, ця стаття не перша, яка висвітлює недоліки мов програмування. Ну і не найкумедніша, сподіваюсь автор мені пробачить.
www.destroyallsoftware.com/talks/wat

Дякую за коментар!
Всі посилання були новими для мене.
Що можете порекомендувати в контексті Unreal + C++?

Читати документацію по Unreal Engine :) Я не писав під анріл, мене ця тема не дуже цікавить.

Під вашу пораду можна підвести таку аналогію з життя.
До вас, інструктора з керування автомобілем, підходить новачок і просить для початку навчити плавно стартувати, впевнено маневрувати на парковці ну і заїзжати задом в гараж. А ви даєте йому талмуд з конструкцією конкретного автомобіля і кажете, що для початку треба прочитати головне з цієї книжки ;).
Документація — це в першу чергу опис бібліотеки, який дозволяє зробити все, що було закладено в engine при проектуванні. Вміти нею користуватися безсумнівно дуже важливо, коли почав трошки орієнтуватися у різноманітті інтсрументів, які ти отримав. Але починати одразу з документації — ненайпростіший і ненайефективніший метод.
Unity я починав вчити з досить непоганої книжки, а потім вже були документація і форуми.
По Unreal я не знайшов ні гарної спеціалізованої книжки конкретно для C++ розробників, ні гарних курсів на udemy — все що є, на дуже примітивному рівні.

Я не інструктор з програмування під анріал, тому порівняння хибне. Я абсолютно нічого не знаю навіть про те, як ним користуватись без програмування. :) Мій досвід обмежився розробкою шейдера для triplanar normal mapping — і це було жахливо, робити графами на кілька екранів те, що можна 3 рядками коду це мазохізм.

З тим що ви не інструктор з Unreal сперечатися не буду :).
Але чи згодні ви з тим, що порада новачку починати з документації десь так і виглядає в його очах ;)?
Доречі редактор шейдерів в Unreal вважається еталонним в gamedev і навіть в Unity не так давно прикрутили щось схоже. Такий редактор дозволяє займатися налаштуваннями самим художникам і ті з них, хто з таким редактором працював — у захваті ;).

Мені байдуже що чим вважається. Я особисто вважаю Unity лайном, маючи піврічний досвід роботи, більш ніж вистачило, а те, що від нього всяка школота і гуманітарії «у захваті» мене якось не колишить. Не розумію, які до мене претензії — якщо я не знаю нічого про технологію, я так і кажу, а не надуваю щоки і не розмірковую як космічні човни бороздят просторы большого театра. А порада почитати документацію нікому ще не завадила. На мою думку дискусія в коментах зайшла в тупік і починає мене дратувати.

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

Кроме того, в C# мне нравится располагать рядом приватные данные и публичные методы доступа к этим данным

что нарушает стандарты оформления исходного кода
см. например github.com/...​r/documentation/SA1201.md

ЗЫ: вы всё же ж решили протащить эту безгамотность в качестве «статьи» )) так бывает

Будуть статті від вас щоб показати як треба?

«показать» кому? )) «показать» что?

Статті де ви зможете показати який ви експерт

Если статья будет длинной, её мало кто осилит из-за авторского стиля без запятых :)

C каких пор желание учиться стало порицаемым?
Чем конретно задела вас статья, что вызвала столь негативную реакцию? Вы точно прочитали о ее целевой аудитории в самом начале?
К какой из целевых категорий приведенных в начале вы себя относите? Если к

гуру C++

, то почему вместо примеров безГАмотности ТС, а лучше конструктивного разбора "

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

", вы пытаетесь вешать ярлыки?

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

C каких пор желание учиться стало порицаемым?

у вас там желание учиться или желание учить вы там определитесь ))

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

C++ очень дружелюбный язык. Только друзей он себе выбирает очень тщательно.

Тонкий у вас юмор ;)

Як каже Страуструп — my language, your bugs.

спорим эту «дискуссию» можно закончить просто проверив термин «свободное владение» тс относительно других языках ))

Вот тоже такое подозрение закралось

Повесить ярлык куда проще, чем исследовать биографию, правда ;)?
За 14 лет в gamedev я работал только в 3х компаниях и все они продуктовые, а не галеры, где лохам инстранцам продают мидлов за $6000, а по факту платят $2000. Следовательно каждая считает свои деньги и кого попало не набирает. Спросите у Ваэля, владельца Frogwares, скольким сотрудникам он платил в момент увольнения премию вообще. Мне, когда я увольнялся по собственнному желанию спустя 3.5 года работы, он не только долгов по зп не оставил, но и выплатил еще 4 месячных оклада премии. Скорее всего за красивые глаза.
В текущей компании я работаю 4 года, сами посчитаете, сколько я проработал в Wargaming до этого?
Помнится в 2010 году, до момента покупки «Першої Студії» «кровавым» Wargaming, мы короткий период аутсорсили для Electronic Arts — делали так называемые дрилы под Wii, XBox360, PS3 на кривых тулза EA. Они сами знали, что тулзы кривые, что только подготовка окружения и ресурсов под каждый новый дрилл занимает около рабочего дня, потому приемлимым считалось 5 дней на выполнение каждого и для этого требовался 1 программист. Одновременно в работе было порядка 7-8 дрилов/программистов. А потом «несвободно владеющий языками» TC взял и по собственной инициативе создал тулзу, которая позвоила каждому программисту заканчивать дрил за день. Прибавку к зп он снова получил за красивые глаза ;). А потом еще его, от нефиг делать естесно, выбрали одним из 5 программистов начинать проект для Wargaming и так он этот проект до релиза и доводил полностью отвечая за всю боевку на сервере и плотно работая с клиентскими программерами.
В текущей компании без меня вышли на steam, но на PS4 и XBox выходили уже со мной. Вы много знаете чисто украинских компаний, который вышли на 3 таких платформы?
Вот пример «бездарного» проектного решения TC, которое NDA незапрещает раскрыть:
stackoverflow.com/...​keyword/37238655#37238655
100 пудово каждый джун знает зачем нужны приватные интерфейсы.
Если вы внимательно не вчитывались в целевую аудиторию статьи — можете очень коротко прочитать здесь, почему у нее оказался такой заголовок и почему примеры на столько простые.
dou.ua/...​ign=reply-comment#1853645

Повесить ярлык куда проще, чем исследовать биографию, правда ;)?

Так таки да. Вы мою биографию исследовали? Я только крайние 14 лет работаю в одной компании. И как пришел на С++ — так и продолжаю. Несмотря на некоторые (удачные) проекты на C#. И Unity, кстати, тоже освоил. Но это так, для себя и сына, хобби.

Кстати, моя жена дружит с топ менеджеркой Wargaming Александрой из Латвии (у них тут в Никосии шикарнейший офис), могу поспрашивать за некоего М. Комисаренко. Можно будет сравнить, если она вообще вкурсе кто это.

но и выплатил еще 4 месячных оклада премии. Скорее всего за красивые глаза.

Видимо, этот пассаж должен был произвести впечатление о Вас как нев?безно крутом программисте... но он не произвел. Не на меня, во всяком случае. Я дважды в год получаю двойной оклад в премию на протяжении лет 10, но как-то мне не приходило в голову этот факт коррелировать со своим владением или невладением С++. Мне за результат платят, а не за «знания».

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

Вы много знаете чисто украинских компаний, который вышли на 3 таких платформы?

А Вы много знаете украинских программистов зарабатывавших программированием тысячи советских рублей в годах эдак 1988-1990х, например?

Вы в моем тексте где-то увидели претензии к вашей квалификации как С++ программиста? Ткните носом, если не сложно.
Тем не менее вы подписались под наездом, целью которого было дискредитировать меня как программиста вообще и дальше пытаетесь давить авторитетом.
Специально для вас процитирую:

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

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

Вы абсолютно точно пишите:

Но это херня всё, я лично знаю людей, гораздо лучше меня знающих С++ в частности и умеющих программировать вообще.

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

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

Вдруг вы так далеко «неосилили» :-\

В тексте маленька ошибка, предполагалось

но обязательно ЛИ ему платить премию? А если на это наложить вашу особенность характера болезненно относится к уходу каждого сотрудника?
Тем не менее вы подписались под наездом, целью которого было дискредитировать меня как программиста вообще и дальше пытаетесь давить авторитетом.

То, как Вы расписали «проблемы» языка С++ может свидетельствовать о том, что Вы и в других языках не очень разбираетесь. Просто в других языках Вам не приходится глубоко копать для достижения приемлемого результата, поэтому Вас все устраивает. Вот под этим я подписался. Но я бы не считал это наездом. В нашем деле главное — результат, а не круче всех разбираться в языке. Если Ваш уровень устраивает Ваших работодателей — то и славно, результат достигнут. А вот замахиваться на анализ «проблем» языков все же не стОит.

Может ли студент чему-то научить школьника? А джун студента? А мидл джуна? А один техэкспер другого?
Вы считаете не должно быть статей для новичков, от немного более опытных?
Мне кажется эксперту спустя годы сложно понять, что будет просто, а сложно начинающим, особенно, когда он с ними особо не работает.
Я много работаю с новичками — мне нравится помогать тем, кто реально хочет учиться.
Мне приходилось помогать студентам наших ВУЗов, один был из Гамбурга, один из Сиднея, один из Дублина. С удовольствием помогаю сотрудникам, если вижу, что могу быть им полезен. Я не зря написал, что самоучка, так как в политехе я учился не на программиста, но все время учебы делал работы для всех курсов. Меня никто не учил, все что знаю — нашел и разобрался сам. Потому мне проще понять, что именно вызывает проблемы на каждом этапе освоения той или иной проблемы, фреймворка, языка.
Да, у меня в знаниях можно найти пробелы — но я всегда уважал стремления к знаниям другим и сам не собираюсь останавливаться на достигнутом.

Может ли студент чему-то научить школьника? А джун студента? А мидл джуна? А один техэкспер другого?

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

Меня никто не учил, все что знаю — нашел и разобрался сам.

Я тоже. И именно поэтому я бы не стал серьезно рассуждать о недостатках того или иного языка. Для таких рассуждений нужен либо офигенный теоретический бэкграунд, либо опыт в создании языков и написании компиляторов. Иначе выходит как у Вас: Вам нравится писать public/private прямо перед декларацией — и Вы выписываете отсутствие такой практики в недостатки языка (хотя возможность такая есть, Вам уже сказали — просто пишите public: declaration; — разница с C# только в «лишнем» двоеточии). Подобные пассажи сильно занижают ценность статьи в целом.

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

И тем не менее рекомендуете мне не продолжать это делать ;)?
Не буду спорить — тоже понимаю, что статься не идеальна, особенно в свете некоторых уточнений, о которых я не знал, но о которых написали в комментах более опытные.
Если не брать во внимание местами не удачную стилистику повествования, считаете ли вы, что я не зря потратил кучу времени (пару десятков вечеров)? Может ли такая статья быть полезной целевой аудитории?

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

рекомендуете мне не продолжать это делать ;)?

Хотите учить — да ради Бога. Только всегда помните, что учение, как медицина, очень ответственное дело.

Для того, чтобы понять, что ее нужно было так позиционировать, понадобилось сначала поговорить со спецами и их я нашел только здесь ;). В том я и видел пользу для себя — площадка для диалога.
Статья проходила техническое рецензирования — особых претензий по ней мне не написали. Здесь в комментариях тоже в пух и прах ее не разносили — следовательно шанс, что кому-то она принесет пользу, есть.
Спасибо за критику.

Подписаться на комментарии