LINUX.ORG.RU

Как перейти с новомодных недоязычков на православные C/C++?

 , ,


0

5

Честно, меня не беспокоит отсутствие вменяемых инструментов работы с юникодом. Меня в C и C++ отталкивают две вещи.

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

Вторая - заголовочные файлы. Как-то раз, давно, я писал простенькое приложение. Я создал заголовочный файл, в котором хранились глобальные (для всего приложения) значения. Подключаю его в нескольких .cpp файлах - линкер ругается, не может он что-то там слинковать. Добавляю в заголовочник #pragma once - компилятор ругается, что у многих .cpp нет к нему доступа. Бомбанул, перестал писать. Конечно, жопы прожжёных сишников и крестовиков и не такое видали, но я не выдержал.

При чём тут новомодные недоязычки? В них такого нет. Их компиляторы умеют разруливать такие вещи. Понятно, что C создавался ещё тогда, когда компьютеры могли только по одному файлу построчно читать код, и все эти видимости функций были оправданы. Но уже в конце 90х и начале 00х были огромные проекты. Операционные системы, браузеры, игры. Памяти в компьютерах уже должно было хватить на то, чтобы обрабатывать огромные объёмы информации. Да даже сейчас есть огромные жирные проекты (привет, Chrome), которые написаны на C++ и требуют по 16 гигов оперативки для сборки. Неужели наличие таких «особенностей» - преимущество перед другими языками?

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

Как перейти с новомодных языков на C/C++ и перестать бомбить с описанных в посте проблем?


Да, я знаю, что C и C++ - очень разные языки. Но всё же это некая основа программирования во всём мире, поэтому я упомянул их вместе.



Последнее исправление: Original_1 (всего исправлений: 2)
Ответ на: комментарий от trex6

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

Lrrr ★★★★★
()
Ответ на: комментарий от Lrrr

А serde - компилирующееся полчаса говно на процедурных макросах

+100500

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

fsb4000 ★★★★★
()

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

Во-вторых, твои проблемы решаются стандартно:

  • .cpp/.h файлы всегда идут парами, в .h объявления всех функций, в .cpp - реализации. Соответственно, после препроцессинга все объявления всегда идут перед реализациями и никаких ошибок не возникает
  • Разве что в отдельных случаях если нужно объявить функцию в заголовке (например, шаблон), а также с типами могут понадобиться forward declaration’ы
  • С переменными и константами паттерн такой
    • В C и легаси C++: в .h extern [const] int foo; в его .cpp [const] int foo = 123;
    • В современном C++ - inline [const|constexpr] int foo = 123; в заголовке и всё.
slovazap ★★★★★
()
Ответ на: комментарий от slovazap

.cpp/.h файлы всегда идут парами, в .h объявления всех функций, в .cpp - реализации.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alegz

не в курсе разницы между определением и объявлением

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

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

trex6 ★★★★★
()
Ответ на: комментарий от alysnix

потому что не понимаешь, как это работает под капотом.

Тот же самый вопрос. Зачем, зачем эти знания программисту. Компилятор в силах сам сгененрировать внешние интерфейсы к модулю (еденице трансляции). Намного проще указать зависимость от конкретной еденицы модуля (use/import) чем упражнятся в склейке текстовых файлов правильным образом.

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

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 1)
Ответ на: комментарий от Lrrr

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

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

Вопрос не праздный. Я для себя так ничего и не нашел :-(

trex6 ★★★★★
()
Ответ на: комментарий от trex6

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

ЭТО ВОЗМУТИТЕЛЬНО!11

thesis ★★★★★
()
Ответ на: комментарий от trex6

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

С дуба рухнул? Ни на одном языке невозможно писать эффективный код без знания «разницы между определением и объявлением». Это есть везде.

Внимание, Javascript:

var x;  // declaration
x = 10; // definition

Просто подумай, почему значение x после var x; именно undefined 🤔

fn main() {
  let x; // declaration
  x = 2; // definition
}
fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

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

fn main() {
  let x; // declaration & definition
  x = 2; // initialization
}

Вот что говорит об этом пишет первый попавшийся сайт в интернете

The main difference between Declaration and Definition in C is that declaration of a variable indicates the compiler about the name and the type of the variable, while the definition of a variable indicates the compiler where and how much storage to create for a variable.

Насколько я знаю Rust, let x и объявляет переменную, и сообщает компилятору что для нее необходимо выделить память.

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

Возможно, вы сможете привести пример, как в Rust может выглядеть variable declaration without definition?

С другой стороны, можете прокомментировать (declaration/definition/initialization) код нижу (playground). Это тоже даст мне почву для размышлений.

fn main() {
    let x;
    if std::env::args().len() == 0 {
        x = 0;
    } else {
        x = 1;
    }
    
    println!("X:{x}");
}

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

trex6 ★★★★★
()
Ответ на: комментарий от olelookoe

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

P.S. с ходу не нашел как на нем сделать обработку JSON, но должно быть не сложно.

P.P.S. Не вам, а местным любителям хедеров. Каким образом вам удается понять интерфейс макроса MSGPACK_DEFINE открыв хидер библиотеки? Я даже теряюсь в какую сторону копать чтобы найти его определение без IDE/локальной копии. Даже официальный API reference не очень дружелюбен для новичков.

trex6 ★★★★★
()
Ответ на: комментарий от trex6

let x не дает никаких знаний о природе x, в частности о том, сколько этому x нужно выделить памяти. переменная типа «хз что это» объявлена.

x = 0; - вот тут мы узнаем о том, что же такое этот x. (скорее угадываем, но это неважно). переменная определена. узнав тип переменной x выделяем ей нужное количество памяти и, наконец, присваиваем переменной (теперь уже понятного типа) некое значение. переменная инициализирована.

olelookoe ★★★
()
Ответ на: комментарий от olelookoe

Не совсем так. Type deduction может использовать не только x = 0 для выведения типа переменной.

После выведения типа переменной, компилятор определяет (declaration) переменную и выделяет память (definition) в точке let x.

Таким образом она будет существовать до выполнения одной if для обоих веток. Даже если в одной из веток она не используется.

Вот такой пример должен показать что именно я имею в виду (playground):

fn main() {
    let mut x;
    if std::env::args().len() == 0 {
        x = 0;
        println!("X(1):{x}");
    } else {
        // x = 1;
    }
    
    // память под переменную выделенна 
    // независимо от использованной ветки условного оператора
    x = 42_u16;
    println!("X(2):{x}")
}
trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 1)
Ответ на: комментарий от trex6

Оно ведь умеет работать со вложенными структурами?

// serialize and deserialize nested structure
// {"i":i, "f":f, "a":["str", {"first":1, "second":"two"}]}

// {"first":1, "second":"two"}
struct MyMap {
    MsgPack::str_t key_first; int i;
    MsgPack::str_t key_second; MsgPack::str_t s;
    MSGPACK_DEFINE_MAP(key_first, i, key_second, s);
};

// ["str", {"first":1, "second":"two"}]
struct MyArr {
    MsgPack::str_t s;
    MyMap m;
    MSGPACK_DEFINE(s, m):
};

// {"i":i, "f":f, "a":["str", {"first":1, "second":"two"}]}
struct MyNestedClass {
    MsgPack::str_t key_i; int i;
    MsgPack::str_t key_f; int f;
    MsgPack::str_t key_a;
    MyArr arr;
    MSGPACK_DEFINE_MAP(key_i, i, key_f, f, key_a, arr);
};
olelookoe ★★★
()
Ответ на: комментарий от olelookoe

Супер! Буду пользоваться. Большое спасибо.

trex6 ★★★★★
()
Ответ на: комментарий от trex6

с ходу не нашел как на нем сделать обработку JSON, но должно быть не сложно.

json_to_msgpack

#include <iostream>
#include <iomanip>
#include <nlohmann/json.hpp>

using json = nlohmann::json;
using namespace nlohmann::literals;

int main()
{
    // create a JSON value
    json j = R"({"compact": true, "schema": 0})"_json;

    // serialize it to MessagePack
    std::vector<std::uint8_t> v = json::to_msgpack(j);

    // print the vector content
    for (auto& byte : v)
    {
        std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " ";
    }
    std::cout << std::endl;
}

json_from_msgpack

#include <iostream>
#include <iomanip>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create byte vector
    std::vector<std::uint8_t> v = {0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63,
                                   0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d,
                                   0x61, 0x00
                                  };

    // deserialize it with MessagePack
    json j = json::from_msgpack(v);

    // print the deserialized JSON value
    std::cout << std::setw(2) << j << std::endl;
}
olelookoe ★★★
()
Ответ на: комментарий от alysnix

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

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

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

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Во-первых, это антипаттерн,

«Антипаттерн» - не вытаскивать локальные функции в хидер??? Скрывать реализацию - это антипаттерн? смело!

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

«Антипаттерн» - не вытаскивать локальные функции в хидер??? Скрывать реализацию - это антипаттерн? смело!

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

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

А что, своего мнения и своих мыслей совсем нет?

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Антипаттерн - локальные функции. Я объяснил почему.

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

чтобы еще раз уточнить… то есть по вашему, если снаружи TU нужна только одна функция, но ее реализация(TU) содержит 100 функций… я должен все 100 затащить в хидер?.. просто это настолько невероятное утверждение, что хочется уточнить.

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

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от olelookoe

Не ожидал nlohmann::json поддержку msgpack. Он становится всё лучше и лучше.

rupert ★★★★★
()
Ответ на: комментарий от alysnix

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

Ты невнимательно читаешь. Отказываться от инкапсуляции я не предлагал, я пишу как её делать правильно.

чтобы еще раз уточнить… то есть по вашему, если снаружи TU нужна только одна функция, но ее реализация(TU) содержит 100 функций… я должен все 100 затащить в хидер?.. просто это настолько невероятное утверждение, что хочется уточнить.

Так это абсолютно базовый подход.

Да, именно так, и, во-первых, это никак не влияет на интерфейс твоей TU - он как экспортировал одну функцию так и экспортирует. Если совсем не понятно, то это будет выглядеть, условно, как tu.h, tu.c (1 функция) tu_private.h, tu_private.c (100 функций), причем tu_private.h включается только в tu_private.c. tu.h который единственно определяет интерфейс данного модуля, никак вообще не меняется от того лежат ли функции в tu_private.* или свалены в tu.c. В реальном проекте конечно будут более описательные имена, как-то tu.[ch], tu/math.[ch], tu/codecs.[ch], tu/events.[ch].

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

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

Ну и, наконец, я уже упомянул тестирование. Как ты собрался тестировать свои локальные функции?

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

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

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

В реальном проекте конечно будут более описательные имена, как-то tu.[ch], tu/math.[ch], tu/codecs.[ch], tu/events.[ch].

это совсем не понятно. поскольку TU - это translation unit. трого говоря это один файл подаваемый компилятору. TU воплощает собой раздельную компиляцию в терминах си. программа состоит из TU, и только из них.

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

alysnix ★★★
()
Ответ на: комментарий от trex6

let x и объявляет переменную, и сообщает компилятору что для нее необходимо выделить память.

Это так, но выделение памяти на стеке (alloca в llvm ir) не означает инициализацию. Несмотря на схожесть написания, растовский let x не является аналогом сишного int x. Аналогом будет что-то вроде

такого кода

fluorite ★★★★★
()
Ответ на: комментарий от slovazap

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

С чего это вдруг?

Psilocybe ★★★★
()
Ответ на: комментарий от alysnix

это совсем не понятно. поскольку TU - это translation unit

Здесь это название модуля.

Я же говорил о «модулях», в качестве которых и выступают TU.

Я тоже.

slovazap ★★★★★
()
Ответ на: комментарий от rupert

Я насчитал 5. Это даже больше, чем в C++ (raw, unique_ptr, shared_ptr, weak_ptr).

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

По мне так Раст посложнее C++ будет.

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

DarkEld3r ★★★★★
()
Ответ на: комментарий от fluorite

не означает инициализацию

Вы правы, инициализация (initialization) происходит одоновременно с присваиванием значения. Речь шла про разницу между definition и declaration.

trex6 ★★★★★
()
Ответ на: комментарий от rupert

(raw, unique_ptr, shared_ptr, weak_ptr).

Здесь «указателем» является только raw-указатель. Остальное указателями не является. Если же считать их указателями, то почему не считать reference_wrapper, optional, наконец vector, span и string_view тоже указателями?

Siborgium ★★★★★
()
Ответ на: комментарий от trex6
use std::mem;

struct X<T> {
    data: T, // declaration
}

fn main() {
    let int_x = X { data: 42 }; // definition
    assert_eq!(4, mem::size_of_val(&int_x.data));

    let float_x = X { data: 3.14 }; // definition
    assert_eq!(8, mem::size_of_val(&float_x.data));
}
fluorite ★★★★★
()
Ответ на: комментарий от trex6

Насколько я знаю Rust, let x и объявляет переменную, и сообщает компилятору что для нее необходимо выделить память.

Какого размера?

Psilocybe ★★★★
()
Ответ на: комментарий от Siborgium

Значит утверждение

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

неверно.

let x Сообщает компилятору, что дальнейшее использование идентификатора x не является ошибкой. А память никак в этот момент выделить не может.

Psilocybe ★★★★
()
Ответ на: комментарий от Psilocybe

let x сообщает компилятору пойти поискать сколько памяти нужно выделить для x, а после этого выделить для нее память.

trex6 ★★★★★
()
Ответ на: комментарий от trex6

Нет. let x существует только на фронте, когда о памяти еще даже речи не идет.

Siborgium ★★★★★
()
Ответ на: комментарий от Psilocybe

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

unC0Rr ★★★★★
()
Ответ на: комментарий от trex6

let x = expression (разумеется это зависит от конкретного языка) обычно обьявляет переменную x, с типом результата выражения. и есть синтаксический сахар для конструкций вида.

var x: some_type = expression
или
some_type x = expression

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

alysnix ★★★
()
Ответ на: комментарий от fluorite

На мой взгляд сдесь в кучу свалено: declaration/definition для X (X.data) и declaration + definition для int_x / float_x.

Rust book исользует термин define когда речь идет об описании структуры.

Еще интереснее почитать cppreference.com, где информация излагается достаточно близко к тексту стандарта. Там говорится, что описание структуры c указанием имен/типов ее полей является definition, в противовес declaration == forward declaration, когда определяется имя структуры без указания ее полей.

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 2)
Ответ на: комментарий от trex6

Желание команды разработчиков раст уйти от концепции объявление/определение и всегда использовать слово define понятно. Но не всегда возможно. В примере выше declare относится именно к T. Собственно, даже растбук вынужден здесь применить слово declare.

First, we declare the name of the type parameter inside angle brackets just after the name of the struct. Then we use the generic type in the struct definition where we would otherwise specify concrete data types.

Другой пример, когда от термина «объявление» уйти не удаётся - это объявление вызова функций из библиотек.

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}
fluorite ★★★★★
()
Ответ на: комментарий от fluorite

Другой пример

Good catch! Cекция extern действительно представляет из себя список declarations.

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

В тексте растбука говорится про declare the name of the type parameter (Т). Насколько это относится к полю структуры vs только к типу для меня не очень ясно. Тут надо разбираться как definition/declaration/monomorphization работают друг с другом.

Cпасибо за пищу для размышлений!


Возвращаясь к первоначальному примеру с переменными, вы скорее склоняетесь к моей трактовке:

fn main() {
  let x; // declaration & definition
  x = 2; // initialization
}

или к варианту от @fsb4000:

fn main() {
  let x; // declaration
  x = 2; // definition
}

P.S.

Желание команды разработчиков раст уйти от концепции объявление/определение и всегда использовать слово define понятно.

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

trex6 ★★★★★
()
Ответ на: комментарий от rupert

Сколько в расте разных типов указателей? Я насчитал 5. Это даже больше, чем в C++ (raw, unique_ptr, shared_ptr, weak_ptr). По мне так Раст посложнее C++ будет.

Забыл для плюсов ещё и ссылку и auto_ptr, устаревший, но никуда не девшийся. Итого 6. Получается, по-твоему, плюсы таки сложнее?

А если серьёзно, смотреть надо в другое место.

Когда плюсовый type* может означать:

  • просто указатель для работы с низкоуровневой хренью
  • указатель на массив неизвестной длины
  • указатель на интерфейс неизвестного реального типа

и ещё пару разных концепций, то может всё-таки стоило бы завести разных указателей?

khrundel ★★★★
()
Ответ на: комментарий от khrundel

auto_ptr, устаревший, но никуда не девшийся

Удалён давно.

N4190 Removing auto_ptr, random_shuffle(), And Old <functional> Stuff

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

Ах, нет, gcc libstdc++ не удалили, но можно им в баг трекер написать, что не реализуют стандарт.

https://gcc.godbolt.org/z/q37h8dPYM

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)

Имхо единственная причина в нынешних временах ввязываться в с/с++ это добиться большей производительности в ограниченных ресурсах (arm и тд) или высокопроизводительные задачи

Описанные вами ужасы больше на хайп похожи

wolverin ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.