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

Сообщество C++ дополняет стандарт чаще, чем Apple выпускает новые iPhone. Благодаря этому C++ теперь больше похож на большого слона, а съесть целого слона за один присест невозможно. Вот почему я решил написать эту статью, чтобы дать вашему путешествию по современному C++ своего рода отправную точку. Моя целевая аудитория здесь — люди, которые переходят со старого (т.е. 98/03) С++ на современный (т.е. 2011 и далее) С++.

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

Разделители разрядов чисел

int no = 1'000'000;                      // визуальное разделение единиц, тысяч, миллионов и т.д.
long addr = 0xA000'EFFF;                 // визуальное разделение 32-битного адреса на
uint32_t binary = 0b0001'0010'0111'1111; // удобочитаемые сегменты
  • Раньше вам нужно было считать цифры или нули, но, начиная с C++14, вы можете сделать большие числа намного нагляднее.

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

  • Благодаря сгруппированным разрядам, ваш код станет немного выразительнее.

Псевдонимы типов

template <typename T>
using dyn_arr = std::vector<T>;
dyn_arr<int> nums; // эквивалентно std::vector<int>

using func_ptr = int (*)(int);
  • Семантически похоже на использование typedef, однако псевдонимы типов легче читаются и совместимы с шаблонами С++. Поблагодарите С++11.

Пользовательские литералы

using ull = unsigned long long;

constexpr ull operator"" _KB(ull no)
{
    return no * 1024;
}

constexpr ull operator"" _MB(ull no)
{
    return no * (1024_KB);
}

cout<<1_KB<<endl;
cout<<5_MB<<endl;
  • По большей части это будут какие-нибудь реальные единицы, такие как kb, mb, км, см, рубли, доллары, евро и т.д. Пользовательские литералы позволяют вам не определять функции, для выполнения преобразования единиц измерения во время выполнения, а работать с ним как с другими примитивными типами.

  • Очень удобно для единиц и измерения.

  • Благодаря добавлению constexpr вы можете добиться нулевого влияния на производительность во время выполнения, что мы увидим позже в этой статье, и более подробно вы можете почитать об этом в другой статье, которую я написал, — “Использование const и constexpr в С++”.

Унифицированная инициализация и инициализация нестатических членов

Раньше вам нужно было инициализировать поля их значениями по умолчанию в конструкторе или в списке инициализации. Но начиная с C++11 можно задавать обычным переменным-членам класса (тем, которые не объявлены с ключевым словом static) инициализирующее значение по умолчанию, как показано ниже:

class demo
{
private:
    uint32_t m_var_1 = 0;
    bool m_var_2 = false;
    string m_var_3 = "";
    float m_var_4 = 0.0;

public:
    demo(uint32_t var_1, bool var_2, string var_3, float var_4)
        : m_var_1(var_1),
          m_var_2(var_2),
          m_var_3(var_3),
          m_var_4(var_4) {}
};

demo obj{123, true, "lol", 1.1};
  • Это особенно полезно, когда в качестве полей выступают сразу несколько вложенных объектов, определенных, как показано ниже:

class computer
{
private:
    cpu_t           m_cpu{2, 3.2_GHz};
    ram_t           m_ram{4_GB, RAM::TYPE::DDR4};
    hard_disk_t     m_ssd{1_TB, HDD::TYPE::SSD};

public:
    // ...
};
  • В этом случае вам не нужно инициализировать их в списке инициализации. Вместо этого вы можете напрямую указать значение по умолчанию во время объявления.

class X
{
    const static int m_var = 0;
};

// int X::m_var = 0; // не требуется для статических константных полей
  • Вы также можете инициализировать во время объявления const static члены класса, как показано выше.

std::initializer_list

std::pair<int, int> p = {1, 2};
std::tuple<int, int> t = {1, 2};
std::vector<int> v = {1, 2, 3, 4, 5};
std::set<int> s = {1, 2, 3, 4, 5};
std::list<int> l = {1, 2, 3, 4, 5};
std::deque<int> d = {1, 2, 3, 4, 5};

std::array<int, 5> a = {1, 2, 3, 4, 5};

// Не работает для адаптеров
// std::stack<int> s = {1, 2, 3, 4, 5};
// std::queue<int> q = {1, 2, 3, 4, 5};
// std::priority_queue<int> pq = {1, 2, 3, 4, 5};
  • Присваивайте значения контейнерам непосредственно с помощью списка инициализаторов, как это можно делать с C-массивами.

  • Это справедливо и для вложенных контейнеров. Скажите спасибо С++11.

auto & decltype

auto a = 3.14; // double
auto b = 1; // int
auto& c = b; // int&
auto g = new auto(123); // int*
auto x; // error -- `x` requires initializer
  • auto-типизированные переменные выводятся компилятором на основе типа их инициализатора.

  • Чрезвычайно полезно с точки зрения удобочитаемости, особенно для сложных типов:

// std::vector<int>::const_iterator cit = v.cbegin();
auto cit = v.cbegin(); // альтернатива

// std::shared_ptr<vector<uint32_t>> demo_ptr(new vector<uint32_t>(0);
auto demo_ptr = make_shared<vector<uint32_t>>(0); // альтернатива
  • Функции также могут выводить тип возвращаемого значения с помощью auto. В C++11 тип возвращаемого значения должен быть указан либо явно, либо с помощью decltype, например:

template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y)
{
    return x + y;
}
add(1, 2);     // == 3
add(1, 2.0);   // == 3.0
add(1.5, 1.5); // == 3.0
  • Приведенная выше форма определения возвращаемого типа называется trailing return type, т.е. -> return-type.

Циклы for по диапазону

  • Синтаксический сахар для перебора элементов контейнера.

std::array<int, 5> a {1, 2, 3, 4, 5};
for (int& x : a) x *= 2;
// a == { 2, 4, 6, 8, 10 }
  • Обратите внимание на разницу при использовании int в противовес int&:

std::array<int, 5> a {1, 2, 3, 4, 5};
for (int x : a) x *= 2;
// a == { 1, 2, 3, 4, 5 }

Умные указатели

  • C++11 добавляет в язык новые умные указатели: std::unique_ptr, std::shared_ptr, std::weak_ptr.

  • А std::auto_ptr устарел, и в конечном итоге удален в C++17.

std::unique_ptr<int> i_ptr1{new int{5}}; // Не рекомендуется 
auto i_ptr2 = std::make_unique<int>(5);  // Так лучше

template <typename T>
struct demo
{
    T m_var;

    demo(T var) : m_var(var){};
};

auto i_ptr3 = std::make_shared<demo<uint32_t>>(4);

nullptr

  • C++11 добавил новый тип пустого указателя, предназначенный для замены макроса C NULL.

  • nullptr имеет тип std::nullptr_t и может быть неявно преобразован в типы непустых указателей, и в отличие от NULL, не конвертируем в целочисленные типы, за исключением bool.

void foo(int);
void foo(char*);
foo(NULL); // ошибка -- неоднозначность
foo(nullptr); // вызывает foo(char*)

Строго типизированные перечисления

enum class STATUS_t : uint32_t
{
    PASS = 0,
    FAIL,
    HUNG
};

STATUS_t STATUS = STATUS_t::PASS;
STATUS - 1; // больше не валидно, начиная с C++11
  • Типобезопасные перечисления, которые решают множество проблем с C-перечислениями, включая неявные преобразования, арифметические операции, невозможность указать базовый тип, загрязнение области видимости и т.д.

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

  • Приведение в стиле C изменяет только тип, не затрагивая сами данные. В то время как старый C++ имел небольшой уклон в типобезопасность, он предоставлял фичу указания оператора/функции преобразования типа. Но это было неявное преобразование типов. Начиная с C++11, функции преобразования типов теперь можно сделать явными с помощью спецификатора explicit следующим образом:

struct demo
{
    explicit operator bool() const { return true; }
};

demo d;
if (d);                             // OK, вызывает demo::operator bool()
bool b_d = d;                       // ОШИБКА: не может преобразовать 'demo' в 'bool' во время инициализации
bool b_d = static_cast<bool>(d);    // OK, явное преобразование, вы знаете, что делаете
  • Если приведенный выше код кажется вам странным, то можете прочитать мой подробный разбор этой темы — “Приведение типов в С++”.

Move-семантика

  • Когда объект будет уничтожен или не будет более использоваться после выполнения выражения, целесообразнее переместить (move) ресурс, а не копировать его.

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

  • Рассмотрим следующую функцию, меняющую местами два значения:

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // теперь у нас есть две копии a
    a = b;      // теперь у нас есть две копии b (+ отброшена копия a)
    b = tmp;    // теперь у нас есть две копии tmp (+ отброшена копия b)
}
  • Использование move позволяет вам напрямую обменивать ресурсы вместо их копирования:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}
  • А теперь представьте, что происходит, когда Т это, скажем, vector<int> размера n. И n достаточно велико.

  • В первой версии вы читаете и записываете 3*n элементов, во второй версии вы в по сути читаете и записываете только 3 указателя на буферы векторов плюс 3 размера буферов.

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

  • Эта фича даст вам значительный прирост в производительности — именно то, поэтому люди используют C++ (т.е., чтобы выжать последние 2-3 капли скорости).

Универсальные ссылки

  • В официальной терминологии известные как forwarding references (передаваемые ссылки). Универсальная ссылка объявляется с помощью синтаксиса Т&&, где Т является шаблонным параметром типа, или с помощью auto&&. Они в свою очередь служат фундаментом для двух других крупных фич:

    • move-семантика

    • И perfect forwarding, возможность передавать аргументы, которые являются либо lvalue, либо rvalue.

Универсальные ссылки позволяют ссылаться на привязку либо к lvalue, либо к rvalue в зависимости от типа. Универсальные ссылки следуют правилам свертывания ссылок:

  1. T& & становится  T&  

  2. T& && становится T&

  3. T&& & становится T&

  4. T&& && становится T&&

Вывод шаблонного параметра типа с lvalue ​​и rvalue:

// Начиная с C++14 и далее:
void f(auto&& t) {
  // ...
}

// Начиная с C++11 и далее:
template <typename T>
void f(T&& t) {
  // ...
}

int x = 0;
f(0); // выводится как f(int&&)
f(x); // выводится как f(int&)

int& y = x;
f(y); // выводится как f(int& &&) => f(int&)

int&& z = 0; // ПРИМЕЧАНИЕ: z — это lvalue типа int&amp;&amp;.
f(z); // выводится как f(int&& &) => f(int&)
f(std::move(z)); // выводится как f(int&& &&) => f(int&&)
  • Если вам это кажется сложным и странным, тогда для начала прочитайте это, а затем возвращайся обратно.

Шаблоны с переменным количеством аргументов

void print() {}

template <typename First, typename... Rest>
void print(const First &first, Rest &&... args)
{
    std::cout << first << std::endl;
    print(args...);
}

print(1, "lol", 1.1);
  • Синтаксис ... создает пакет параметров или расширяет уже существующий. Шаблонный пакет параметров — это шаблонный параметр, который принимает ноль или более аргументов-шаблонов (нетипизированных объектов, типов или шаблонов). Шаблон С++ с хотя бы одним пакетом параметров называется вариативный шаблоном с переменным количеством аргументов (variadic template).

constexpr

constexpr uint32_t fibonacci(uint32_t i)
{
    return (i <= 1u) ? i : (fibonacci(i - 1) + fibonacci(i - 2));
}

constexpr auto fib_5th_term = fibonacci(6); // равноценно auto fib_5th_term = 8
  • Константные выражения — это выражения, вычисляемые компилятором во время компиляции. В приведенном выше примере функция fibonacci выполняется/вычисляется компилятором во время компиляции, и будет заменена на результат в вызове места.

  • Я написал подробную статью, раскрывающую эту тему, “Использование const и constexpr в С++”.

Удаленные и дефолтные функции

struct demo
{
    demo() = default;
};

demo d;

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

class demo
{
    int m_x;

public:
    demo(int x) : m_x(x){};
    demo(const demo &) = delete;
    demo &operator=(const demo &) = delete;
};

demo obj1{123};
demo obj2 = obj1; // ОШИБКА -- вызов удаленного конструктора копирования
obj2 = obj1;      // ОШИБКА -- оператор = удален

В старом С++ вы должны были сделать его приватным. Но теперь в вашем распоряжении есть директива компилятора delete.

Делегирование конструкторов

struct demo
{
    int m_var;
    demo(int var) : m_var(var) {}
    demo() : demo(0) {}
};

demo d;
  • В старом C++ вам нужно создавать функцию-член для  инициализации и вызывать ее из всех конструкторов для достижения универсально инициализации.

  • Но начиная с C++11 конструкторы теперь могут вызывать другие конструкторы из того же класса с помощью списка инициализаторов.

Лямбда-выражения

auto generator = [i = 0]() mutable { return ++i; };
cout << generator() << endl; // 1
cout << generator() << endl; // 2
cout << generator() << endl; // 3
  • Я думаю, что эта фича не нуждается в представлении и является фаворитом среди других фич.

  • Теперь вы можете объявлять функции где угодно. И это не будет стоить вам никаких дополнительных накладных расходов. 

  • Я написал отдельную статью на эту тему — “Разбираемся с лямбда-выражениями в C++ на примерах”.

Операторы ветвления с инициализатором

  • В более ранних версиях C++ инициализатор либо объявлялся перед оператором и просачивался во внешнюю область видимости, либо использовалась явная область видимости.

  • В C++17 появилась новая форма if/switch, которую можно записать более компактно, а улучшенный контроль области видимости делает некоторые ранее подверженные ошибкам конструкции немного более надежными:

switch (auto STATUS = window.status()) // Объявляем объект прямо в операторе ветвления
{
case PASS:// делаем что-то
    break;
case FAIL:// делаем что-то
    break;
}
  • Как это работает

{
    auto STATUS = window.status();
    switch (STATUS)
    {
    case PASS: // делаем что-то
        break;
    case FAIL: // делаем что-то
        break;
    }
}

std::tuple

auto employee = std::make_tuple(32, " Vishal Chovatiya", "Bangalore");
cout << std::get<0>(employee) << endl; // 32
cout << std::get<1>(employee) << endl; // "Vishal Chovatiya"
cout << std::get<2>(employee) << endl; // "Bangalore"
  • Кортежи представляют собой набор разнородных значений фиксированного размера. Доступ к элементам std::tuple производится с помощью std::tie или std::get.

  • Вы также можете выхватывать произвольные и разнородные возвращаемые значения следующим образом:

auto get_employee_detail()
{
    // делаем что-нибудь . . . 
    return std::make_tuple(32, " Vishal Chovatiya", "Bangalore");
}

string name;
std::tie(std::ignore, name, std::ignore) = get_employee_detail();
  • Используйте std::ignore в качестве плейсхолдера для игнорируемых значений. В С++ 17, вместо этого следует использовать структурированные привязки.

Выведение аргумента шаблона класса

std::pair<std::string, int> user = {"M", 25}; // раньше
std::pair user = {"M", 25};                   // C++17

std::tuple<std::string, std::string, int> user("M", "Chy", 25); // раньше
std::tuple user2("M", "Chy", 25);                               // выведение в действии!
  • Автоматическое выведение аргументов шаблона очень похоже на то, как это делается для функций, но теперь также включает и конструкторы классов.

Пара слов в заключение 

Здесь мы только слегка коснулись огромного набора новых фич и возможности их применения. В современном C++ можно найти еще очень много чего, но тем не менее вы можете считать этот набор хорошей отправной точкой. Современный C++ расширяется не только с точки зрения синтаксиса, но также добавляется гораздо больше других функций, таких как неупорядоченные контейнеры, потоки, регулярное выражение, Chrono, генератор/распределитель случайных чисел, обработка исключений и множество новых алгоритмов STL (например, all_of(), any_of(), none_of(), и т.д).

Да пребудет с вами C++!


Завтра вечером пройдет открытое занятие, посвященное Boost. На уроке вы узнаете, как подключать Boost в проект с помощью cmake; познакомитесь подробнее с библиотеками Boost и научитесь их использовать. Записаться на урок можно на странице курса "C++ Developer. Professional".

прк

Комментарии (135)


  1. Kelbon
    13.06.2023 15:03
    +17

    этим "фичам" около 16 лет уже


    1. Leetc0deMonkey
      13.06.2023 15:03
      +9

      Моя целевая аудитория здесь — люди, которые переходят со старого (т.е. 98/03) С++ на современный (т.е. 2011 и далее) С++.


      1. NeonMercury
        13.06.2023 15:03
        +16

        Прошу, конечно, прощения, но старый - это C++11, а C++98 - это просто древность


        1. Leetc0deMonkey
          13.06.2023 15:03
          +19

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


          1. Aldrog
            13.06.2023 15:03
            +8

            У нас в проекте техническая возможность перейти на что-то новее C++98 вот только к концу этого года ожидается.


          1. SIISII
            13.06.2023 15:03
            +4

            Это-то понятно, но специалист обычно следит за развитием своего языка, а соответственно, C++11 новым для него не является -- даже если по работе он вообще пишет на чистом L&R C. Не было б в заголовке слова "новая" -- не было бы вопросов. Лучше было б, наверное, "современная" вместо "новая". Ну или что-то в этом роде -- в общем, чтоб не создавало иллюзий, будто речь пойдёт о C++23 или, на худой конец, С++20.


            1. AndreyHenneberg
              13.06.2023 15:03
              +4

              Не буду спорить про точность формулировок, но для меня статья оказалась полезной: я с C++ лет 20 толком не работал (последние 10 лет пишу на Python, до того - PHP, а до того - Java и только где-то на горизонте маячит C++) и недавно обнаружил, что он изрядно изменился. Просто этот язык довольно долго не был "моим", а тут решил, что он мне снова нужен. Статья как раз для таких, как я, кому надо быстро понять, что поменялось и не пытаться изучать язык совсем уж с нуля, чтобы просто читать чужой код.


          1. slonopotamus
            13.06.2023 15:03

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


            1. orfelin
              13.06.2023 15:03
              +1

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


              1. slonopotamus
                13.06.2023 15:03
                +2

                Я в курсе, спасибо, последние 10 лет пишу на плюсах практически ежедневно. Проекты масштабами в миллионы строк кода. Да, некоторые усилия для смены -std=c++-N потребуются, но они бесконечно далеки от "всё переписать".


                1. AndreyHenneberg
                  13.06.2023 15:03

                  Думаю, тут имелось в виду, что появился повод потратить на рефакторинг с наведением современной красоты некоторое, достаточно большое, количество времени. Чтобы не пришлось потом совсем уж с нуля переписывать по причине окончательной потери совместимости с новыми стандартами. А то я с таким сталкивался, пусть и не в проекте с С++, компилятору которого можно просто указать версию стандарта, а когда взялся дописать "пару функций" в проект на Python/Django, а у Django, как оказалось, к тому времени дважды сменилась major-версия, по сравнению с тем, что крутилось на сайте.


          1. 0xd34df00d
            13.06.2023 15:03

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


        1. TalesFromApocalypse
          13.06.2023 15:03

          с возрастом слон только вкуснее


      1. gandjustas
        13.06.2023 15:03

        некоторые программисты родились позже


      1. Pastoral
        13.06.2023 15:03

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


        1. dyadyaSerezha
          13.06.2023 15:03
          +1

          Переходят или не ререходят не люди, а проекты и/или организации.


    1. Nihiroz
      13.06.2023 15:03
      +6

      Я почитал статью, она как раз для меня. Для меня C++ это язык, которым я пользовался только в университете и писал там на C с классами


      1. TalesFromApocalypse
        13.06.2023 15:03
        +1

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


  1. tenzink
    13.06.2023 15:03
    +1

    Для иллюстрации lambda и move семантики я бы предложил другие примеры. На моей практике они заметно чаще встречаются

    Lambdas - сортировка со своим предикатом

    std::vector<A> as = ...;
    auto compare = [](const A& left, const A& right) { return left.x < right.x; };
    std::sort(begin(as), end(as), compare);

    Move семантика - возврат контейнеров по значению теперь вполне идиоматичен:

    std::vector<A> getAs(...);
    auto as = getAs(); // vector is moved (not copied) which is cheap


    1. BeardedBeaver
      13.06.2023 15:03
      +1

      Второй пример это не столько move сколько RVO/copy elision


      1. tenzink
        13.06.2023 15:03

        RVO/NRVO - оптимизация, которой может и не случиться. А вот move сработает гарантированно


        1. vamireh
          13.06.2023 15:03
          +3

          В C++17 RVO - это не просто оптимизация, а обязанность компилятора в оговорённых случаях, даже если у типа нет конструкторов копирования/перемещения. Как можно видеть, в C++14 такого ещё нет.


          1. tenzink
            13.06.2023 15:03

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


            1. Aldrog
              13.06.2023 15:03
              +1

              Move-семантика только один из механизмов, обеспечивающих идиому возврата дорого копируемых объектов по значению, и если изначально она была ключевой, то с C++17 отошла на второй план и даже в определенных случаях стала антипаттерном (когда стандарт гарантирует RVO, ухудшает производительность).


              1. tenzink
                13.06.2023 15:03

                Не очень понял про антипаттерн. Слово антипаттерн подразумевает, что есть способ писать код, как делать не надо. Можете привести пример?

                Могу предположить что не нужно делать `return std::move(x)`, но это и до C++17 считалось чушью


              1. Kelbon
                13.06.2023 15:03

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


  1. allcreater
    13.06.2023 15:03

    Спасибо за статью!

    Больше C++, хорошего и разного.

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

    А суть в общем-то проста - это именно семантика, позволяющая писать разный код для работы со временными и "долговременными" объектами, rvalue и lvalue. Именно благодаря знанию о сущности аргумента оператора присваивания или конструктора можно написать гораздо более эффективный код, в случае того же std::vector'а позволяющий просто(* за редкими исключениями наподобие несовместимых аллокаторов) обменять указатели на реальный массив вместо честного поэлементного копирования.


  1. dyadyaSerezha
    13.06.2023 15:03
    +6

    До сих пор убивает это наследние C -

    void f(int &a) {...}

    Тип a - это int&, но можно писать (и часто пишут) & рядом с a, а не с int.

    Здесь мы только слегка коснулись огромного набора новых фич

    А вот тут самая фишка - огромное количество уже существующих и новых и новых фич. Язык становится монструозным.


    1. simenoff
      13.06.2023 15:03
      +3

      Пора запилить C+++


    1. BenGunn
      13.06.2023 15:03
      +1

      Я тоже считаю, что писать надо int& a. Но всегда найдутся староверы с int &a. В любом случае надо следовать правилам принятым в конкретном проекте, имхо.


      1. kovserg
        13.06.2023 15:03
        +9

        struct A {
            int &a, &b;
            A(int &a,int &b) : a(a), b(b) {}
        };

        и где тут лучше & писать?


        1. geher
          13.06.2023 15:03
          +3

          Вроде как по последним веяниям так

          int &a, &b;

          писать нехорошо.

          Надо так

          int &a;

          int &b;

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

          Да и собака - это вроде @.


          1. domix32
            13.06.2023 15:03
            +1

            В какой-то момент стал отбивать амперсанд вообще с двух сторон и писать const после типа:

            auto & var;
            auto const& var; 
            for (auto const& [k,v]: map) { ... }


      1. SIISII
        13.06.2023 15:03
        +3

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

        Но вообще, синтаксис Си отвратителен, а синтаксис объявлений -- отвратителен вдвойне.


        1. domix32
          13.06.2023 15:03

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


      1. rukhi7
        13.06.2023 15:03

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

        вот насколько я знаю есть смысл различать:

        int & a

        int * a

        int ? a

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

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

        int & a

        int * a

        int ? a


        1. unreal_undead2
          13.06.2023 15:03
          +1

          Вопрос скорее в том, насколько интуитивно читать объявление типа "int& a, b;"


          1. rukhi7
            13.06.2023 15:03
            -2

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

            Потом вот вы написали:

            int& a, b;

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

            должно быть:

            int& a = something;

            насколько я помню, соответственно Б туда не влезет. НО -

            кого это волнует, главное пробелы правильно расставить.


            1. unreal_undead2
              13.06.2023 15:03
              +1

              Ok, int& a=b, c=d;


              1. dyadyaSerezha
                13.06.2023 15:03
                +1

                А не надо так писать вообще.


                1. slonopotamus
                  13.06.2023 15:03

                  Не надо добавлять в язык конструкции, которые вообще не надо использовать.


        1. geher
          13.06.2023 15:03
          +1

          Стесняюсь спросить, а что такое

          int ? a

          ?

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


    1. Daddy_Cool
      13.06.2023 15:03
      +6

      >Язык становится монструозным.
      ТОЛЬКО СТАНОВИТСЯ?
      ---
      Чак Норрис программирует на С++. На С++2099.


      1. funny_falcon
        13.06.2023 15:03
        +1

        Чак Норрис не программирует на С++2099. При виде Чака Норриса, С++2099 программирует себя сам.


    1. vk6677
      13.06.2023 15:03

      Согласен, что язык монструозный.

      int a = 0, b = 0;
      int &c = a, d = b;

      Если не ошибаюсь, то c - ссылка на переменную a. В b - переменная типа int. И вот в таком случае рядом с чем писать символ "&" ? (В реальной жизни никогда бы так не написал, что-бы не ввести в заблуждение).


      1. dyadyaSerezha
        13.06.2023 15:03
        +1

        Ещё раз - это жуткое наследие С и вообще не надо так писать.


        1. kovserg
          13.06.2023 15:03

          Правильно надо так


          typedef int& int_ref;

          или по сиплюсплюсному


          template<typename T> struct ref { typedef T& type; };
          //...
          void fn( ref<int>::type a );


          1. ksbes
            13.06.2023 15:03
            +2

            Т.е. от 5-9 значащих символов на объявление переменной к почти сотне. Отличный прогресс! Таким языком обязательно все будут восхищаться!


          1. eao197
            13.06.2023 15:03

            template<typename T> struct ref { typedef T& type; };

            Э...

            template<typename T>
            using ref = T&;
            


            1. unreal_undead2
              13.06.2023 15:03

              Выглядит как то неконсистентно с std::enable_if или std::remove_cv. Да и вообще зачем изобретать велосипед, уже есть std::add_lvalue_reference


              1. eao197
                13.06.2023 15:03

                Т.е. вы предлагаете писать:

                void fn(std::add_lvalue_reference<int>::type a) {...}
                


                1. unreal_undead2
                  13.06.2023 15:03

                  Не уверен, что это финальный вариант, но по крайней мере похоже на современный C++ )


                  1. eao197
                    13.06.2023 15:03

                    O_o. У меня современный C++, наверное, какой-то другой системы :)

                    Я в принципе не понял, зачем @kovserg ввел тип ref. Вероятно, в каких-то сценариях это полезно. Но, имхо, сделано это было каким-то замороченным способом, можно было бы и проще.


                    1. unreal_undead2
                      13.06.2023 15:03

                      Я в принципе не понял

                      Похоже, вы в принципе шуток не понимаете )


                      1. eao197
                        13.06.2023 15:03

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


        1. unreal_undead2
          13.06.2023 15:03

          Если в блоке кроме объявления переменных (которых, скажем, штук пять и все int/int&) ещё одна строчка, разбивать объявление на несколько тоже не шибко красиво, проще всё-таки показать пробелами, где у нас ссылка, а где простой int.


  1. Leetc0deMonkey
    13.06.2023 15:03

    Кстати дефолтовый конструктор это совсем не пользовательский пустой {}. Например дефолтовый проделает zero-initialization своих мемберов, а пустой уже не сделает этого.


    1. unreal_undead2
      13.06.2023 15:03

      А чем =default отличается от отсутствия явно задекларированного конструктора (в классе/структуре без других конструкторов, как в примере в статье)?


      1. Leetc0deMonkey
        13.06.2023 15:03

        Конкретно в том примере непонятно зачем оно там.


      1. 0xd34df00d
        13.06.2023 15:03

        Смотря где написано = default — внутри определения класса или отдельно.


        1. unreal_undead2
          13.06.2023 15:03

          Предположим что внутри, как в примере (я ж в к примеру придираюсь, а не к ключевому слову как таковому).


      1. dyadyaSerezha
        13.06.2023 15:03

        Разве что тем, что указание default явно говорит, что конструктор не забыли)


        1. unreal_undead2
          13.06.2023 15:03

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


          1. vamireh
            13.06.2023 15:03

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

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


            1. unreal_undead2
              13.06.2023 15:03

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


          1. dyadyaSerezha
            13.06.2023 15:03

            Вот именно. Хотел чел написать кастомные конструктор/деструктор, но забыл. А компилятор молча сгенерил дефолтные. Баг налицо.


            1. unreal_undead2
              13.06.2023 15:03

              Если дефолтный не нужен, то пригодится скорее deleted.


    1. Kelbon
      13.06.2023 15:03

      нет, конструктор всегда лучше делать = default, а вот

      int i;
      и
      int i{};
      различаются, в первом случае неинициализировано, во втором 0


  1. kovserg
    13.06.2023 15:03

    А в каком стандарте впервые появился фича pointer provenance ?


    1. domix32
      13.06.2023 15:03

      Это кажется касается С в первую очередь, не С++. nullptr_t появился в 11 стандарте, поэтому возможно тогда же и provenance завёлся.


  1. webhamster
    13.06.2023 15:03
    +5

    Итак, судьба снова свела вас с C++,

    Я не хотел этого!

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

    Нет. Нет. Нет.


    1. dyadyaSerezha
      13.06.2023 15:03

      Поражён, но явно не этим)


  1. SpiderEkb
    13.06.2023 15:03
    +7

    Выскажу непопулярное мнение.

    Сообщество C++ дополняет стандарт чаще, чем Apple выпускает новые iPhone. Благодаря этому C++ теперь больше похож на большого слона, а съесть целого слона за один присест невозможно.

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

    И порой возникает ощущение, что стандарт развивается быстрее, чем компиляторы.
    По мне так лучше было бы развивать тот же LLVM и возможности линковать в одну программу модули на разных языках. Удобнее написать этот кусок на Rust, Go или чем-то еще? Пишем на то, на чем удобнее и линкуем модулем.

    Когда-то очень нравился pure C за его простоту, лаконичность и почти неограниченные возможности (да, там легко было выстрелить себе в ногу, но на то и дана голова чтобы этого не делать).

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

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

          dcl-ds t_dsArray qualified template;
            Array  char(70) dim(arrSize) ascend;
              Key  like(t_Customer)  overlay(Array: 1);
              Data like(t_CustData)  overlay(Array: 10);
              CUS  char(6)           overlay(Array: 1);
              CLC  char(3)           overlay(Array: 7);
              CPNC char(6)           overlay(Array: 10);
              CUN  char(35)          overlay(Array: 16);
              CRF  char(20)          overlay(Array: 51);
          end-ds;

    Array - массив строк по 70 символов. Каждая строка структурирована - первые 6 символов CUS, потом три символа CLC, 6 символов CPNC, 35 символов CUN и 20 символов CRF. Кроме того, CUS + CLC рассматривается как ключ (Key), остальное - как связанные с ним данные (Data).

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

                cntCustomers += 1;
                
                dsArray.CUS(cntCustomers)  = dsGF.GFCUS; 
                dsArray.CLC(cntCustomers)  = dsGF.GFCLC;
                dsArray.CPNC(cntCustomers) = dsGF.GFCPNC;
                dsArray.CUN(cntCustomers)  = dsGF.GFCUN;
                dsArray.CRF(cntCustomers)  = dsGF.GFCRF;

    А дальше - хотим работаем со всей строкой целиком, хотим - с отдельными ее элементами.

    Да, на С++ все это можно сделать - позволяет. Без проблем. Но придется написать свой класс, с конструктором, методами и т.п. А тут все просто на уровне определения структуры данных.

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

    Опять же, мое личное мнение, без претензий на абсолютную истину.


    1. hydroargerum
      13.06.2023 15:03
      +1

      Спасибо утащил к себе в фонд цитат и в рамочку и на стену "..Это проблема. Просто потому, что "сообщество" пытается втащить в С++ все, что увидело в других языках..". Самая точная характеристика этих частых стандартов


      1. SpiderEkb
        13.06.2023 15:03
        +1

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

        Как пример - появились корутины (Go, C# и т.п.) - надо обязательно втащить их в С++ в очередном стандарте... И покопаться - такого сплошь и рядом.

        Последние 6 лет работаю в банке, платформа IBM i (центральные сервера, ядро АБС, вся бизнес-логика). Основной язык - RPG (ровестник Кобола, тоже для коммерческих вычислений, но до сих пор активно развивается и сейчас это вполне полноценный процедурный язык). Пример описания структур выше - из него. Там вообще много интересного есть на уровне языка.

        Но суть не в этом. Суть в том, что на платформе реализована концепция ILE - интегрированная языковая среда. Это значит что можно написать модуль на С/С++, модуль на RPG и объединить их в один программный объект ("модуль" здесь есть аналог объектного файла). Главное правильно прототипы функций реализованных на исходном языке модуля описать на целевом языке откуда они будут вызываться:

        extern "C" _RPG_ind USRQ_Create(char* __ptr128 Name, char* __ptr128 Lib, eQueueType eSeq, 
                                        char* __ptr128 Desc, int nMsgSize, int nKeyLen, int nInitMsgs, 
                                        int nExtMsgs, int nMaxMsgs, char* __ptr128 pError);

        Это исходная функция на С (на __ptr128 не обращаем внимание - это заморочка, связанная с разными моделями памяти - в одной указатели 128 бит, в другой - 64 бита)

        extern "C" тут просто чтобы с манглингом не заморачиваться (хотя и это возможно - просто придется правильное имя в extproc прописывать)

        dcl-pr USRQ_CreateQueue ind extproc(*CWIDEN : 'USRQ_Create') ;
          Name      char(10)                   const;                                  // Имя очереди
          Lib       char(10)                   const;                                  // Библиотека
          eSeq      int(10)                    value;                                  // Тип очереди queKeyd/queLIFO/queFIFO
          Desc      char(50)                   const;                                  // Описание
          nMsgSize  int(10)                    value;                                  // Макс. размер сообщения
                                                                                       // Максимально допустимое значение - 64000 байт
          nKeyLen   int(10)                    value;                                  // Размер ключа (игнорируется для не queKeyd)
                                                                                       // Максимально допустимое значение - 256 байт
          nInitMsgs int(10)                    value;                                  // Начальное количество сообщеий
          nExtMsgs  int(10)                    value;                                  // Колчество сообщений в приращении
          nMaxMsgs  int(10)                    value;                                  // Максимальное количество сообщений
          Error     char(37)                   options(*omit);                         // Ошибка
        end-pr;

        И ее прототип на RPG позволяющий вызывать ее из RPG модулей.

        Более того, из RPG можно вызывать любую функцию из С-шной библиотеки просто описав ее протип.

        Например, в RPG не завезли генератор ПСЧ. Ну и что? Пишем:

              dcl-pr Random int(10) extproc('rand');
              end-pr;

        и пользуемся

        rndIdx = %rem(Random(): upLimit) + 1;

        И вот работа в таких условиях вполне комфортна - бизнес-логику удобнее писать на RPG, но если нужно что-то низкоуровневое (или просто удобнее и эффективнее на С сделать) - пишем на С.

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


        1. mayorovp
          13.06.2023 15:03
          -1

          Как пример — появились корутины (Go, C# и т.п.) — надо обязательно втащить их в С++ в очередном стандарте… И покопаться — такого сплошь и рядом.

          Да, надо было. На самом деле это надо было сделать с самого начала. Сопрограммы — довольно древняя концепция, и их совершенно зря позабыли при создании ЯВУ. Кстати, двигались они по языкам довольно медленно, заново появились-то они впервые в Python 2.2 в 2001м году.


          на платформе реализована концепция ILE — интегрированная языковая среда

          Судя по тому, что вы пишете дальше, это фича не столько Си/С++, сколько языка RPG.


          И я не вижу никаких принципиальных отличий от FFI, реализованного в Rust, Java, C#, Go и Haskell. Вот буквально всё написанное вами делается в любом современном языке.


          1. SpiderEkb
            13.06.2023 15:03

            Судя по тому, что вы пишете дальше, это фича не столько Си/С++, сколько языка RPG.

            Нет. Это фича платформы. Все компиляторы тут (CL, COBOL, RPG, C/C++) на выходе дают универсальный TIMI код (код в т.н. "машинных инструкциях" - это не ассемблер, выше уровнем, ближе к набору низкоуровневых системных команд и функций). И биндеру (линковщику) потом уже все равно на каком языке это было изначально написано.

            Это ровно то же самое, что реализовано в LLVM.


            1. mayorovp
              13.06.2023 15:03
              +2

              Рад за них, но на других платформах FFI прекрасно работает и без универсального кода.


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


          1. domix32
            13.06.2023 15:03
            +1

            принципиальных отличий от FFI, реализованного в Rust, Java, C#, Go и Haskell.

            Потому что везде это старый добрый С ABI и пока никто ничего принципиально нового не изобрёл. В расте на свой ABI забили, а в WASM пока ещё WIP, хотя местами и есть разные черновые варианты.


            1. mayorovp
              13.06.2023 15:03
              +1

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


          1. funny_falcon
            13.06.2023 15:03

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


            1. kovserg
              13.06.2023 15:03
              +2

              Такое интервью :)


              Интервью Bjarne Stroustrup

              HACKNET REVIEW 01/98
              Интервью Bjarne Stroustrup, данное 1 января 1998 года
              для журнала Computer.
              © 1998, Computer
              перевод: Mike Bluesman


               Первого Января 1998 года Bjarne Stroustrup давал интервью журналу 'Computer'. Вообще-то редакторы предполагали, что он расскажет о семи годах объектно-ориентированного программирования с применением языка, который он и разработал.
               К окончанию беседы выяснилось, что интервьюер извлек больше информации, чем предполагал, и, естественно, редакторы решили урезать содержание 'для пользы индустрии', но, как обычно получается в таких случаях, произошла утечка информации.
               Вот полный и нередактированный протокол интервью - это не похоже на обычные запланированные вопросы/ответы.
              Вам наверняка покажется это интересным.

              Интервьюер — далее И., Stroustrup — далее C..


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


              C. Вообще-то я думал об этих днях как раз перед тем как Вы приехали. Помните — все писали свои версии 'C', и проблема была в том, что все это делали чертовски замечательно. Университеты тоже чертовски замечательно преподавали этот язык. Это привело к понижению компетенции. Под 'компетенцией' в данном случае я подразумеваю феноменальность. Вот что породило проблему.


              И. Проблему?


              C. Да, проблему. Помните когда все писали на Cobol?


              И. Конечно, я тоже это делал.


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


              И. Да уж, вот это были времена...


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


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


              С. Точно. То же самое случилось и с программистами, писавшими на 'C'.


              И. Понятно, ну и что же Вы все-таки хотите этим всем сказать?


              C. Однажды я сидел у себя в оффисе, и мне пришла в голову небольшая идейка, как хоть немного восстановить баланс. Я подумал: интересно, что произойдет, если будет язык программирования такой запутанный и такой сложный для изучения, что никто бы уже не сможет заполнить рынок толпой программистов, пишуших на этом нем? У меня уже были тогда кое-какие мысли по этому поводу. Вот, знаете наверно, X10 и X windows. Это тогда была такая графическая система, которая работала на Sun 3/60. У нее были все ингредиенты, которые мне были нужны — комплексный синтаксис, сложные для понимания мрачные функции, псевдо объектно-ориентированная структура. Даже сейчас никто не пишет напрямую под X-windows. Motif — единственный путь, если вы хотите сохранить рассудок.


              И. Шутите?


              C. Ничуть. Есть еще одна проблема. Unix был написан на 'C' — это значило то, что любой программист, пишущий на 'C', мог очень легко стать системным программистом. Помните сколько обычно зарабатывали большинство системных программистов?


              И. Да, я же ведь тоже этим занимался.


              С. Так вот, этот новый язык должен был отделять себя от Unix путем скрывания всех системных вызовов, которые так здорово связывают 'C' и Unix. Тогда ребята, знающие только DOS, тоже смогли бы прилично зарабатывать.


              И. Не верится в то, что Вы это сказали...


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


              И. Ну расскажите поточнее, как же Вы все-таки сделали это?


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


              И. Что?


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


              И. Ну, вообще-то не слышал, но...


              С. Вот так-то. Некоторые, кстати, пытались. Была такая компания из Орегона — Mentor Graphics, в которой просто заболели тем, что пытались переписать все что можно на C++ в '90 или '91 году. Я на самом деле им сочувствовал, но думаю, что люди по крайней мере, научились чему-то на их ошибках.


              И. Очевидно у них ничего не вышло?


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


              И. Так все-таки у них получилось? Это доказывает что 'объектное-ориентирование' работает.


              C. Почти. Запускаемый файл получился такой огромный, что загружался 5 минут на рабочей станции HP со 128Mb оперативной памяти. Я думал, что это станет камнем преткновения, но это никого особенно не заботило. Sun и HP были очень рады продавать до ненормальности мощные ящики с огромными ресурсами для выполнения на них тривиальных программ. Знаете, когда мы в AT&T; откомпилировали нашим первым компилятором C++ программку 'Hello World', я не мог поверить своим глазам: запускаемый файл получился размером 2.1Mb.


              И. Да уж… Но компиляторы с тех пор прошли долгий путь.


              C. Вы так думаете? Попробуйте тот же пример 'Hello World' с последней версией g++ — вы получите примерно пол-мегабайта. А кроме этого есть еще множество примеров со всего мира. У British Telecom чуть было не возникли большие проблемы, но к своему счастью они вовремя догадались свернуть проект и начать все заново. И им больше повезло, чем Australian Telecom. А теперь я слышал, что Siemens cоздает какого-то динозавра и все больше и больше волнуется по поводу размера того, что у них получается. Не правда ли забавно смотреть на это всеобщее заблуждение?


              И. Да, но C++ -то, в общем, вполне нормальный язык.


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


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


              С. Не совсем так. У каждого есть его выбор. Я не предполагал, что все это так выйдет из-под контроля. Но все-равно, практически все у меня получилось. C++ cейчас уже умирает, а труд програмистов продолжает нормально оплачиваться — особенно тех, кто имеет дело со всей этой чепухой — вы же понимаете, что невозможно использовать эффективно большой программный модуль на C++, если не вы сами его написали.


              И. Как это?


              С. Не понятно что-ли? Помните typedef ?


              И. Конечно.


              С. А теперь вспомните сколько времени приходится копаться в заголовках для того, например, чтобы просто найти, что какое-нибудь там 'RoofRaised' — число с двойной точностью. Представьте теперь сколько времени уйдет на нахождение всех определений типов в большом проекте.


              И. Значит, Вы утверждаете, что Вам все, что Вы хотели удалось...


              C. Ну, вспомните сколько занимает реализация проекта среднего размера на 'C'. Это около 6 месяцев. Не достаточно долго чтобы парень с женой и детьми мог заработать себе на нормальное существование. Попробуйте тот же проект реализовать на C++, и что получится? Вам понадобится 1-2 года. Не правда ли, это замечательно? Кроме этого: в университетах уже так давно не преподают 'C', что теперь стало мало людей программирующих на 'C', особенно таких, которые знают все о программировании под Unix. Как вы думаете: сколько парней смогут сообразить что делать с 'malloc', после того как втечение многих лет они пользовались 'new' и никогда не заботились о проверке кода возврата? Большинство программистов на C++ вообще не выбрасывают этот код возврата. Что произошло со старой доброй '-1'? По крайней мере было сразу понятно, что у тебя где-то ошибка без всяких там 'throw', 'try' и 'catch'...


              И. И все же, наследование экономит кучу времени?


              С. Нет, я же говорил… Замечали, в чем разница между стадиями планирования проектов на 'C' и C++? Для проекта на C++ эта стадия в три раза дольше. Время уходит на то, чтоб убедиться что все что надо наследуется, а все что не надо — нет. И все-равно без ошибок не обходится. Кто слышал когда-нибудь об утечке памяти в программе на 'C'? Теперь нахождение этих утечек — целый труд. Большинство компаний сдаются, так и выпускают продукт, зная что утечка памяти существует.


              И. Но есть различные программные инструменты...


              С. Большинство из которых написаны на C++.


              И. Если мы опубликуем все это, то Вас просто могут линчевать, понимаете ?


              C. Сомневаюсь. Как я сказал C++ уже уходит в прошлое. Ни одна компания без предварительного тестирования теперь не начнет проект на C++, а если будет тестирование, то они поймут, что это путь к неудаче. Если не поймут — то так им и надо. Знаете, я пытался убедить Dennis'a Ritchie переписать Unix на C++.


              И. О Боже. И что же он сказал?


              C. К счастью у него присутствует хорошее чувство юмора. Я думаю и он, и Brian понимали что я тогда делал. Он ответил, что может мне помочь написать версию DOS на C++, если я захочу.


              И. Ну и как? Вы захотели?


              С. Я написал DOS на C++. Могу дать вам demo. Она у меня работает на Sparc 20 в другой комнате. Просто летает на четырех процессорах и занимает всего то 70 мегабайт на диске.


              И. На что же это похоже на PC ?


              С. Вы, очевидно, шутите. Видели же вы Windows'95? Я о них думаю как о своем величайшем успехе.


              И. Знаете, эта идея насчет Unix++ заставила меня задуматься. Ведь где-то может сидеть парень, которому придет в голову сделать это...


              С. Но не после того, как он прочитает это интервью.


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


              С. Но это же история века. Я просто хотел чтоб мои приятели-программисты помнили меня за то, что я для них сделал. Знаете как сейчас оплачивается программирование на C++ ?


              И. Последнее, что я слышал — настоящие профессионалы зарабатывают $70-80 в час.


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


              И. Имеете ввиду, что раньше Вам C++ не нравился?


              С. Ненавидел его. Он даже выглядит неуклюже, вы не согласны? Но когда стали там выходить разные книги… вот, тогда-то я и увидел полную картину.


              И. Погодите, а как насчет ссылок? Вы подтверждаете что улучшили указатели 'C' ?


              С. Хмм. Я и сам не знаю. Вообще я думал, что да. Потом я как-то говорил с парнем, который писал на C++ с самого начала. Он говорил, что не мог запомнить были ли ссылки на его переменные или нет, поэтому он всегда использовал указатели.


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


              С. Пообещайте мне, что опубликуете это.


              И. Я извещу Вас, но мне кажется, что я знаю, что скажет мой редактор по этому поводу.


              С. А все-равно, кто этому поверит? Кстати, не могли бы вы мне прислать копию этой записи?


              И. Это я могу.


              Примечание переводчика :


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


              специальный перевод для Hacknet Review выполнил Mike Bluesman, март 1998


      1. nickolaym
        13.06.2023 15:03
        +2

        "Этих частых стандартов"? Смешно.
        Посмотрите хотя бы на версии питона, и поймёте, что комитетчики - те ещё лютые тормоза.
        И кстати, комитетчики лихо отбиваются от разных пропозалов "я это видел в другом языке (в хаскелле, например!) и хочу такое же тут". В стандарт попадают довольно консервативные вещи, иногда это даже бесит - три года обсуждали гору, родили мышь.


    1. codecity
      13.06.2023 15:03
      +1

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

      Это проблема не только C++, но и практически всех популярных языков. Посмотрите C# - сколько там парадигм добавили помимо деревьев выражений. Даже JS во что превратился... Может разве что Python более консервативен, но с ним не приходилось работать.


    1. eao197
      13.06.2023 15:03
      +3

      современный С++ проигрывает чистому С в производительности и использовании ресурсов

      И следом несколько пруфов...

      Как нет?

      (и постоянное динамическое выделение памяти не всегда хорошо и исключения не всегда быстро)

      И следом несколько пруфов...

      Как нет?

      Например

      И тут пример, на котором проиграют (практически) все универсальные языки, включая упомянутый C, а так же Java, C#, Python, JavaScript и т.д.

      Но претензию нужно предъявить C++, да.


      1. SpiderEkb
        13.06.2023 15:03

        Пруфы, увы, привести не смогу - вряд ли кому-то здесь что-то скажут данные, полученные из PEX (Performance EXplorer) Так что придется поверить на слово.
        Нагрузочное тестирование у нас очень жесткое (т.к. hiload) и насмотрелся на то, как слишком активная динамическая работа с памятью отъедает ресурсы процессора.

        До банка работа в АСУТП. И там приходилось в некоторых местах отказываться от удобных и универсальных stl реализаций в пользу самописных, заточенных под конкретную задачу. Просто потому что любая универсальность притащит хоть немного, но оверхеда. И готовые решения просто "не вывозили" по скорости.

        Аналогично с исключениями. Пока они работают в рамках одного объекта - все ок. Как начинаем выводить их за рамки объекта (где все это реально нужно) - получаем просадку в производительности (по PEXам опять же). И в добавок ко всему, языковые средства обработки исключений работают только с языковыми исключениями и не ловят системные. И зачем оно мне, когда у меня есть возможность напрямую, средствами системных API кинуть в любом месте системное исключение и средствами системных же API его перехватить и обработать там, где это реально нужно?

        Но претензию нужно предъявить C++, да.

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

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

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


        1. mayorovp
          13.06.2023 15:03

          Есть стандарт на ваши изделия. Вы работаете строго по нему. И в один прекрасный момент заказчик Вам заявляет — "я не приму вашу продукцию т.к. она не соответствует новому стандарту".

          Это проблемы разработчиков компиляторов. И, в общем-то, именно они и принимают новые стандарты. Так в чём же проблема-то?


        1. eao197
          13.06.2023 15:03
          +5

          Так что придется поверить на слово.

          Ну прямо классика: "А у нас джентльменам верят на слово. И тут мне карта как поперла!"
          Видите ли, мне довелось увидеть достаточно C++ного кода, написанного в стиле:

          std::string * v = new std::string(...);
          ...
          delete v;
          

          или:

          void f(std::string v) { // Передача по значению, да.
            ... // Здесь v только читается и никуда не копируется/переносится.
          }
          std::string some_value = ...;
          f(some_value);
          

          чтобы со скепсисом относиться к утверждениям про тормознутость С++.

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

          И переписывали вы "stl реализации" на чем, неужели на чистом Си?

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

          Готовые решения, в частности, стандартная библиотека (особенно та ее часть, которая касается контейнеров) создаются для того, чтобы удовлетворить целому ряду критериев, где оверхед (разного типа, не только по скорости) всего лишь часть из этих самых критериев. И особенность C++ (для кого-то плюс, для кого-то минус) в том, что STL является такой же библиотекой, как и все остальные (за редким-редким исключением). И если где-то одна библиотека (STL) не устраивает, ее можно заменить другой оставаясь в рамках того же языка.

          Пока они работают в рамках одного объекта - все ок. Как начинаем выводить их за рамки объекта (где все это реально нужно) - получаем просадку в производительности

          Складывается ощущение, что у вас какие-то странные представления об исключениях. Даже в рамках одного объекта (что бы это не означало) исключения вам просадят производительность в разы, а то и на порядок-другой, если начнут выскакивать чаще, чем в 1-2% случаев.

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

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

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

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

          Но если это не так, то как на ваше мнение нужно реагировать?

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

          Э... Даже если оставить в стороне тот факт, что тенденция создавать среду, в которой можно бесшовно использовать несколько языков, развивается уже лет 30, наверное (только на моей памяти начиная с появления в Windows OLE, которое потом стало COM). И на современные результаты можно посмотреть в экосистемах .NET и JVM. Даже не смотря на это, есть вот какая сторона медали:

          даже если вы пишете на языке X в среде, где запросто могут уживаться еще и языки Z, Y, W, то все равно остается вопрос о том, на насколько удобно/просто/безопасно/эффективно писать именно на X, если таки нужно писать именно на X?

          Вот нужно вам использовать C++ (не суть важно почему) и все. Вот нужно.

          И что, вы готовы еще лет 20 программировать на C++98? Или даже на C++11?

          Я нет. Мне даже на C++14 после C++17 возвращаться бывает тяжело (if constexpr нет, structured bindings нет, [[nodiscard]] нет, ...). А уж застрять на C++98... Мне столько не заплатят, чтобы добровольно согласиться на такое.


          1. SpiderEkb
            13.06.2023 15:03
            -1

            Мне столько не заплатят, чтобы добровольно согласиться на такое.

            Ну а мене не заплатят столько чтобы я кинулся на С++ реализовывать работу со сложными структурами данных (приводил пример - и это еще достаточно простой случай). Или проверку корректности даты в каком-нибудь формате типа *CYMD когда я просто могу написать:

            test(de) *cymd var;
            if %error();
              // это не дата а непойми что
            endif;

            или ждать пока это вдруг войдет в С++ 3999-го года.

            Причем, вместо *cymd может быть любой другой формат - *eur, *iso и т.п.

            Но если это не так, то как на ваше мнение нужно реагировать?

            Можно никак не реагировать :-)

            даже если вы пишете на языке X в среде, где запросто могут уживаться еще и языки Z, Y, W, то все равно остается вопрос о том, на насколько удобно/просто/безопасно/эффективно писать именно на X, если таки нужно писать именно на X?

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

            Вот нужно вам использовать C++ (не суть важно почему) и все. Вот нужно.

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

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

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

            Ну есть у меня код для работы с очередью *USRQ на IBM i. Написан на С. Сможете его собрать под виндой? Нет. По Линуксом? Нет. ни там ни там и близко нет сущности, похоже на *USRQ. Но при этом объект очень полезный и удобный для реализации различного рода конвейеров (да, можно использовать сокеты, пайпы, майлслоты, но там возможностей вполовину меньше - сразу проигрываем в фоункционале)

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

            Да, С/С++ реализованы на всех практически платформах, но это совершенно не значит что код, написанный для одной, будет точно также работать на другой. Ну или придется использовать очень ограниченный набор возможностей и/или кучу #ifdef'ов

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

            Складывается ощущение, что у вас какие-то странные представления об исключениях. Даже в рамках одного объекта (что бы это не означало) исключения вам просадят производительность в разы, а то и на порядок-другой, если начнут выскакивать чаще, чем в 1-2% случаев.

            Это нормально?

            На мой взгляд нет. Исключение - механизм обработки нештатной ситуации. Именно нештатной. Которая совершенно не обязательно ошибка. Как пример - для той же *USRQ есть инструкция deqwait - чтение из очереди с таймаутом. Если очередь пуста - выбрасывается исключение. Системное, не языковое. Средствами С++ не ловится. Только установкой exeption handler. Дальше смотрим по коду исключения и видим что ничего страшного не произошло, просто истекло время отведенное на чтение, но ничего не прочиталось.


            1. eao197
              13.06.2023 15:03
              +5

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

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

              Я бы еще понял, если бы вы пеняли на сложность работы с графовыми структурами (в которых есть циклы); со структурами, где требуется сопоставление с образцом (pattern-matching); с рекурсивными типами (вроде бы не напутал с названием), что-то вроде:

              struct A {
                A left;
                A right;
                ...
              };
              

              и т.д., и т.п.

              Или проверку корректности даты в каком-нибудь формате типа *CYMD когда я просто могу написать:

              Э... А что хотели показать-то? Что в C++ подобное нельзя написать? Или что этого нет в stdlib?

              Ну так в C++ в stdlib, криптографии, например, нет. Как и средств работы с сетью или shared memory. Что не говорит о том, что подобные вещи в C++ не используются.

              Можно никак не реагировать

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

              Суть в том, что нет нужды писать все только на Х.

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

              На мой взгляд это ошибочная парадигма.

              Что вижу вокруг, о том и пою. Задачи могут быть разными, начиная от "у нас старый C++ный код, который падает, течет и тормозит, нужно привести в чувства" и заканчивая тем, что вам нужно сделать свой аналог условного Photoshop или nginx, и альтернатив у C++ в предметной области будет раз-два и все.

              Заказчика абсолютно все равно на чем она написана.

              Заказчику потом с этим кодом жить. И если вы ему напишите на Ada, а не на C++, то у заказчика могут быть потом проблемы с сопровождением того, что вы написали.

              Ну и следуя такой логике комментарии в коде писать не нужно: работает же и это главное.

              Как часто вам приходится писать код, который должен работать на нескольких платформах?

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

              Ну или придется использовать очень ограниченный набор возможностей и/или кучу #ifdef'ов

              Ну придется, и что? Абстракции тут в помощь.

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

              Звучит как "а языка-то я толком и не знаю".

              Это нормально?

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


          1. 0xd34df00d
            13.06.2023 15:03
            +1

            В целом я с вами согласен, но вот здесь — нет:


            И особенность C++ (для кого-то плюс, для кого-то минус) в том, что STL является такой же библиотекой, как и все остальные (за редким-редким исключением).

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


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


            1. eao197
              13.06.2023 15:03
              +1

              Стыковать код, использующий разные реализации словарных типов, тяжело и неэффективно.

              Да.

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

              Не могу судить.

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

              Так же не хочется уходить в ту сторону, что std::string или std::optional -- это словарные типы до тех пор, пока мы находимся в рамках C++ (да и то пока мы не имеем дела с DLL, в каждой из которых своя копия stdlib и свой хип). Как только появляется вопрос интеграции C++ с кодом на других языках, как выяснится, что родной для C++ std::string отнюдь не родной для какого-нибудь Python или Ruby.

              Я все-таки заострю внимание на другом: в stdlib C++ есть немного вещей, для которых нужна поддержка компилятора и которые просто так на свои не заменишь (емнип, std::launder тому пример). Вот в таких местах мы жестко привязаны к stdlib. Тогда как значительную часть STL можно запросто проигнорировать если по каким-то причинам нас эта часть не устраивает.


              1. 0xd34df00d
                13.06.2023 15:03

                Не могу судить.

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


                Т.к. в каких-то случаях тормознутой на каких-то сценариях может оказаться и другая реализация.

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


                (да и то пока мы не имеем дела с DLL, в каждой из которых своя копия stdlib и свой хип).

                О, кроссплатформенность и совместимость подъехали. Но это так, к слову.


                Тогда как значительную часть STL можно запросто проигнорировать если по каким-то причинам нас эта часть не устраивает.

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


                1. eao197
                  13.06.2023 15:03

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

                  Могу лишь предполагать, что на такое поведение пошли для того, чтобы unordered_map был легкой заменой для std::map. Типа был std::map, хлоп, заменили его на unordered_map и не нужно потом блох по коду ловить.

                  Ну да это офтопик. ЕМНИП, доводилось слышать претензии даже к std::vector.

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

                  Мне очень не нравится критиковать действия комитета, поэтому воздержусь.

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

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


    1. rukhi7
      13.06.2023 15:03

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

      Так вроде же есть же? По мне так C# как раз новый язык на базе С++, или нет? Кто скажет против? Интересно почитать опровержения по такому утверждению!


      1. ksbes
        13.06.2023 15:03

        Или нет. C# - это язык на основе Java, вообще-то. Исторически. К тому же ещё и "корпоративный", как Objective-C и Swift. Только недавно "открылся" для других платформ.

        Или вы все "С-подобные" языки относите к языкам на основе "С++"?


        1. rukhi7
          13.06.2023 15:03
          +1

          C# - ... Только недавно "открылся" для других платформ.

          Так открылся же, какая разница когда? Получается теперь какбы и не "корпоративный".

          Или вы все "С-подобные" языки относите к языкам на основе "С++"?

          А что с этим не так? по моему это логично, относить все "С-подобные" языки к языкам на основе "С++"

          на основе Java, вообще-то. Исторически

          исторически вы имеете ввиду что позже появился? а на что это "исторически" влияет?


          1. ksbes
            13.06.2023 15:03
            +1

            Так открылся же, какая разница когда? Получается теперь какбы и не "корпоративный".

            Ну так протолкнёте какую-нибудь полезную фичу? Без Микрософта? Был и есть корпоративный: запретят завтра всем на нём программировать - никуда не денемся.

            по моему это логично, относить все "С-подобные" языки к языкам на основе "С++"

            Ну как бы смешивать понятия синтаксиса и языка ... неумно, что ли?

            исторически вы имеете ввиду что позже появился?

            Нет. Значит в истории развития языков довольно чётко записано , что С# появился именно как аналог Java, после разборок Microsoft и Oracle.

            а на что это "исторически" влияет?

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


            1. rukhi7
              13.06.2023 15:03

              Ну как бы смешивать понятия синтаксиса и языка ... неумно, что ли?

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

              Значит в истории развития языков довольно чётко записано ...

              Кем записано, когда записано, где это можно прочитать?

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

              тех кто разбирается в предмете, естественно.

              Это похоже на какую то секту, извините.

              Хорошо, понятно.


              1. eao197
                13.06.2023 15:03
                +2

                Это похоже на какую то секту

                Секта тех, кто наблюдал за историей Visual J++ и терок между Microsoft и Sun.

                Нужно сказать, что расширять языки своими нестандартным конструкциями в 1990-е были модно. В C++ это делали и Microsoft, и Borland. Microsoft по привычке и на Java таким образом покусилась, но получила по зубам от Sun.


                1. rukhi7
                  13.06.2023 15:03
                  +3

                  Microsoft по привычке и на Java таким образом покусилась, но получила по зубам от Sun.

                  Я только не понимаю почему это называется "получила по зубам", по мне так C# намного лучше чем Java.

                  И кстати после С и С++ все как то выглядит очень естественно на С#, вплоть до указателей в Unsafe моде. (хотя у меня, конечно, предвзятое мнение, я все также сижу в Вижал Студии)

                  У меня когда то был опыт работы на С++ через встроенный SQL с базами данных (библиотекой что ли это назвать-не знаю, скорее расширение компилятора), так LINQ это просто песня по сравнению с тем гемороем.


                  1. eao197
                    13.06.2023 15:03
                    +1

                    Я только не понимаю почему это называется "получила по зубам"

                    ЕМНИП, MS пыталась продвигать J++ как Java для Windows, акцентируясь именно на том, что это почти та же самая Java. А Sun запретила это делать. И выяснилось, что у MS нет безопасного и быстрого языка с GC для своей платформы вместо C++. Поэтому и потребовалось рожать C#.

                    Если бы Sun более лояльно отнеслась к нарушению максимы "write once, run everywhere", то история могла бы пойти по совсем другому сценарию.


                    1. rukhi7
                      13.06.2023 15:03

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

                      J++ как Java для Windows

                      , а пришлось назвать С#.

                      Не велика потеря для истории, все таки, как мне кажется.


                      1. eao197
                        13.06.2023 15:03
                        +2

                        Боюсь ошибиться, но когда MS начала переделывать Java под себя, будущий автор C#, Андерс Хейлсберг, еще трудился в Borland над Delphi. А без него C# вряд ли бы случился. Может быть было что-то другое, но C# -- это детище Хейлсберга.


              1. tolik_anabolik
                13.06.2023 15:03
                +1

                Автор комментария выше, говоря о том что C# появился как копия Java, в чем-то прав. Многие ключевые фишки: динамическая память, gc, jit, generics, exceptions, annotations, web-servlets - C# реализовывал сразу же как они появлялись в Java, некоторые вещи (базовые методы, иерархия исключений) тоже сделаны по аналогии с Java. В принципе нет ничего в этом плохого, всякая идея откуда-то заимствуется.


      1. taishi-sama
        13.06.2023 15:03

        C#(назван в честь ноты, а не потому что C++++) — это в первую очередь Java++ с удачно взятым из Object Pascal разделением объектов на stack-allocated структуры и heap-allocated классы и кучей синтаксического сахара(а ещё с возможностью перегружать операторы и навешивать на всё аттрибуты), и с тем, что это первый "крупный" язык, где появился и синтаксис async/await, и "функциональщина в императивном языке", откуда это и разлетелось по другим языкам.
        C++ в C# же почти нету. Вместо темплейтов дженерики, нет богопротивного множественного наследования классов, и вместо эрзац-интерфейсов в виде классов без реализации — полноценные интерфейсы, вместо RAII и ручного управления памятью — mark-and-sweep GC.(для контроля за другими ресурсами используется using и интерфейс IDisposable)
        В C#, конечно, есть возможность лазить в память через сырые указатели, у которых ещё есть и арифметика указателей(а с .NET 6 — её вручную аллоцировать через malloc/free), но это используется в очень исключительных случаях, и спрятано за блоками unsafe(как в прямом конкуренте C++ — Rust, хоть и Rust вышел в релиз на почти полтора десятка лет позже C#).


        1. Refridgerator
          13.06.2023 15:03

          C# назван в честь ноты
          Тогда и пишите правильно: с♯.


    1. domix32
      13.06.2023 15:03
      +2

      добнее написать этот кусок

      Таки а что вам мешает?

      Array - массив строк по 70 символов. Каждая строка структурирована -
      первые 6 символов CUS, потом три символа CLC, 6 символов CPNC, 35
      символов CUN и 20 символов CRF. Кроме того, CUS + CLC рассматривается
      как ключ (Key), остальное - как связанные с ним данные (Data).

      Вот тут совершенно непонял что написано. Вы хотите какой-то набор байт в структуру десериализовать? Если да, то хоть renitepret_cast используйте,хоть union type, хоть flatbuffers или ещё какую-либу - не понимаю проблемы.

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

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


  1. vvzvlad
    13.06.2023 15:03

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

    Забитых в коде? О_о


    1. unreal_undead2
      13.06.2023 15:03

      Ну почему бы не выложить на github const unsigned long perevod_na_sberbank_ivanov_a_a = 1234'5678'...;


    1. leotsarev
      13.06.2023 15:03
      +1

      В коде тестов


  1. BeardedBeaver
    13.06.2023 15:03

    А почему нет? Обычная ситуация в юнит-тесте

    Ред:

    Промахнулся, отвечал вот сюда https://habr.com/ru/companies/otus/articles/741428/comments/#comment_25648064


  1. webhamster
    13.06.2023 15:03

    Когда объект будет уничтожен или не будет более использоваться после выполнения выражения, целесообразнее переместить (move) ресурс, а не копировать его.

    Ничего не понял. Объект и ресурс - это одно и тоже, или разные вещи? Зачем перемещать ресурс, если он будет уничтожен?


    1. mayorovp
      13.06.2023 15:03
      +1

      На один ресурс могут ссылаться несколько объектов, не обязательно одновременно.


      Но написано и правда странно.


      1. webhamster
        13.06.2023 15:03

        Тогда надо было написать как-то так:

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


  1. webhamster
    13.06.2023 15:03
    +1

    Я не понимаю, почему в обучающих примерах постоянно пляшет инициализация - то через ( ), то через { }. Как человеку, который хочет разобраться, понять, что правильно?


    1. ksbes
      13.06.2023 15:03
      +8

      Разобраться что в С++ правильно? Ха-ха! Желаю удачи!


      1. 0xd34df00d
        13.06.2023 15:03

        Очень просто: в C++ правильно не писать.


    1. knstqq
      13.06.2023 15:03
      +1

      в новом коде предпочитайте всегда использовать {} когда возможно. Из плюсов:

      • сразу видно, что это не вызов функции, а инициализация

      • более строгая проверка типов

      Из минусов:

      • более строгая проверка типов

      • весь старый код переписывать никто не будет

      В обучающих примерах для начинающих не должно быть инициализации через () - разве что где-нибудь вначале сделать дисклеймер, что старый код может использовать () из исторических причин, чтобы знать как читать код. Равно как в обущающих примерах не должно быть printf или auto_ptr, хотя может быть глава/параграф о них.

      @rikki_tikki, обновите пожалуйста здесь, () неуместны:

      std::tuple user2("M", "Chy", 25);

      и здесь:

      T tmp(std::move(a));


      1. eao197
        13.06.2023 15:03

        в новом коде предпочитайте всегда использовать {} когда возможно
        В обучающих примерах для начинающих не должно быть инициализации через () - разве что где-нибудь вначале сделать дисклеймер, что старый код может использовать () из исторических причин

        Не все так просто, к сожалению: https://wandbox.org/permlink/DQ1f2ypC39vHfzeu


        1. knstqq
          13.06.2023 15:03

          увы, но да, это 2 совсем разных конструктора.

          неявный initializer_list в таком синтаксисе и к тому же везде-где-только-можно в stl в таком виде ИМХО был ошибкой в С++11


          1. eao197
            13.06.2023 15:03

            Типа того.

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

            • если там нет конструкторов с initializer_list или же все конструкторы принимают initializer_list, то использовать {};

            • если там есть и конструкторы с initializer_list, и конструкторы без оных (как в случае std::vector, std::basic_string), то выбирать между {} и () так, чтобы случайно не вызвать конструктор с initializer_list когда это не нужно.

            Правда, здесь еще нужно сделать ремарку касательно aggregate initialization для структур, у которых вообще нет явно описанных конструкторов... В общем, не так все просто, как хотелось бы :(


            1. slonopotamus
              13.06.2023 15:03
              +1

              следует посмотреть на список его конструкторов

              Вы предлагаете написать код, который в будущем пойдёт по одному месту из-за того что в тип добавили конструктор, причём код начнёт молча делать что-то другое. Звучит как раскладывание граблей. Зачем?


              1. eao197
                13.06.2023 15:03

                Зачем?

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

                ИМХО, конструктор с initializer_list нужен классам-контейнерам или мимикрирующим под них. Соответственно, там подобные конструкторы должны появляться сразу как часть интерфейса.


      1. Leetc0deMonkey
        13.06.2023 15:03

        Тут не всё так просто. {} можно перепутать с aggregate initialization и initializer list. Это даже вызывает проблемы когда раскрываются некоторые шаблоны. Есть мнение что для конструкторов должно быть строго ().


        1. eao197
          13.06.2023 15:03

          Есть мнение что для конструкторов должно быть строго ().

          Тогда, к сожалению, теряются некоторые проверки, которые делает компилятор при инициализации примитивных типов:

          char c{129};
          


          1. Leetc0deMonkey
            13.06.2023 15:03

            Да, но здесь и не конструктор ведь.


            1. eao197
              13.06.2023 15:03

              Внутри шаблона будет непонятно конструктор это или нет:

              template<typename T>
              void do_something_special_for_me() {
                T c{129};
                ...
              }
              ...
              do_something_special_for_me<char>();
              


              1. Leetc0deMonkey
                13.06.2023 15:03

                Да. Поэтому я упомянул про проблемы в шаблонах. Хотя там это можно порешать определив категорию приехавшего типа.


  1. cdriper
    13.06.2023 15:03

    auto add(X x, Y y) -> decltype(x + y)

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

    auto add(X x, Y y)



  1. excoder
    13.06.2023 15:03

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


  1. nickolaym
    13.06.2023 15:03

    Ещё одна фича explicit - его управляемость. explicit(bool) включает-выключает явное приведение типов.

    template<class T>
    class StonglyTyped {
    public:
      template<class U>
      explicit( ! is_implicit_construction_allowed_v<T,U> )  // напишите предикат сами
      StronglyTyped(U);
    
      template<class V>
      explicit( ! is_implicit_conversion_allowed_v<T,V> )  // напишите предикат сами
      operator V() const;
    };
    


  1. nickolaym
    13.06.2023 15:03
    +3

    Да прибудет с вами C++!

    Куда и когда именно прибудет с нами С++?
    Да пребудет с вами грамотность.


    1. excoder
      13.06.2023 15:03

      "_v". :)