В C++ я плачу за то, что я не ем?

Давайте рассмотрим следующие примеры приветствия в C и C++:

main.c

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}

main.cpp

#include <iostream>

int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}

Когда я компилирую их в godbolt для сборки, размер кода C составляет всего 9 строк (gcc -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret

Но размер кода C++ - 22 строки (g++ -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

... который намного больше.

Известно, что в C++ вы платите за то, что едите. Итак, в этом случае, за что я плачу?

Ответ 1

То, за что вы платите, - это назвать тяжелую библиотеку (не такую тяжелую, как печать на консоли). Вы инициализируете объект ostream. Есть скрытое хранилище. Затем вы вызываете std::endl который не является синонимом для \n. Библиотека iostream помогает вам настраивать множество настроек и возлагать нагрузку на процессор, а не на программиста. Это то, за что вы платите.

Давайте рассмотрим код:

.LC0:
        .string "Hello world"
main:

Инициализация объекта ostream + cout

    sub     rsp, 8
    mov     edx, 11
    mov     esi, OFFSET FLAT:.LC0
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

Вызов cout снова для печати новой строки и сброса

    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xor     eax, eax
    add     rsp, 8
    ret

Статическая инициализация хранилища:

_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

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

Кстати, это всего лишь часть истории. Вы не знаете, что написано в функциях, которые вы вызываете.

Ответ 2

Итак, в этом случае, за что я плачу?

std::cout более мощный и сложный, чем printf. Он поддерживает такие вещи, как локали, флаги форматирования с учетом состояния и т.д.

Если вам это не нужны, используйте std::printf или std::puts - они доступны в <cstdio>.


Известно, что в C++ вы платите за то, что едите.

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

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

Ответ 3

Вы не сравниваете C и C++. Вы сравниваете printf и std::cout, которые могут быть разными (локали, форматирование с сохранением состояния и т.д.).

Попытайтесь использовать следующий код для сравнения. Godbolt генерирует одну и ту же сборку для обоих файлов (тестируется с помощью gcc 8.2, -O3).

main.c:

#include <stdio.h>

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 6; ++i)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

main.cpp:

#include <array>
#include <cstdio>

int main()
{
    std::array<int, 6> arr {1, 2, 3, 4, 5, 6};
    for (auto x : arr)
    {
        std::printf("%d\n", x);
    }
}

Ответ 4

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

Позволяет проверить, что делает ваш код:

C:

  • напечатайте одну строку, "Hello world\n"

C++:

  • переместите строку "Hello world" в std::cout
  • поток std::endl манипулятор в std::cout

Очевидно, ваш C++ код выполняет в два раза больше работы. Для справедливого сравнения мы должны объединить это:

#include <iostream>

int main()
{
    std::cout<<"Hello world\n";
    return 0;
}

... и вдруг ваш код сборки для main выглядит очень похоже на Cs:

main:
        sub     rsp, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        xor     eax, eax
        add     rsp, 8
        ret

Фактически, мы можем сравнить код C и C++ линии за строкой, и есть очень мало различий:

sub     rsp, 8                      sub     rsp, 8
mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                >   mov     edi, OFFSET FLAT:_ZSt4cout
call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor     eax, eax                    xor     eax, eax
add     rsp, 8                      add     rsp, 8
ret                                 ret

Единственное реальное отличие состоит в том, что в C++ мы вызываем operator << с двумя аргументами (std::cout и строка). Мы могли бы удалить даже эту небольшую разницу, используя более близкую C eqivalent: fprintf, которая также имеет первый аргумент, определяющий поток.

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

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

Ответ 5

Известно, что в C++ вы платите за то, что едите. Итак, в этом случае, за что я плачу?

Так просто. Вы платите за std::cout. "Вы платите только за то, что едите", это не значит, что вы всегда получаете лучшие цены ". Конечно, printf дешевле. Можно утверждать, что std::cout более безопасен и более универсален, поэтому его большая стоимость оправдана (она стоит дороже, но обеспечивает большую ценность), но это не соответствует действительности. Вы не используете printf, вы используете std::cout, поэтому вы платите за использование std::cout. Вы не платите за использование printf.

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

Несколько замечаний

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

  2. На самом деле, иногда это даже лучше, чем "в C++ вы платите за то, что едите". Например, компилятор может вывести, что вызов виртуальной функции не требуется в некоторых случаях и преобразовывать его в не виртуальный вызов. Это означает, что вы можете бесплатно получить виртуальные функции. Разве это не здорово?

Ответ 6

"Сборочный листинг для printf" НЕ для printf, но для puts (вид оптимизации компилятора?); printf is prety намного сложнее, чем ставит... не забывайте!

Ответ 7

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

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


абстракция

Итак, в этом случае, за что я плачу?

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

  1. Создание объекта, в основном выделение памяти для самого объекта и его данных.
  2. Инициализация объекта (обычно через некоторый метод init()). Обычно распределение памяти происходит под капотом, как первое, что происходит на этом этапе.
  3. Уничтожение объектов (не всегда).

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

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

Что на самом деле происходит в C++?

Вот оно, разбито:

  1. Класс std::ios_base инициализируется, что является базовым классом для всех связанных с I/O.
  2. Объект std::cout инициализируется.
  3. Ваша строка загружается и передается в std::__ostream_insert, который (как вы уже определили по имени) - это метод std::cout (в основном оператор <<), который добавляет строку в поток.
  4. cout::endl также передается в std::__ostream_insert.
  5. __std_dso_handle передается __cxa_atexit, который является глобальной функцией, которая отвечает за "очистку" перед выходом из программы. __std_dso_handle самой __std_dso_handle чтобы освободить и уничтожить оставшиеся глобальные объекты.

Так что, используя C ==, ничего не платите?

В коде C происходит очень мало шагов:

  1. Ваша строка загружается и передается в puts через edi регистр.
  2. puts вызывается.

Нет объектов в любом месте, поэтому нет необходимости инициализировать/уничтожать что-либо.

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

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

jmp start

msg db "Hello world\n"

start:
    mov rdi, 1
    mov rsi, offset msg
    mov rdx, 11
    mov rax, 1          ; write
    syscall
    xor rdi, rdi
    mov rax, 60         ; exit
    syscall

В основном это приводит к вызову сценария автоматической write а затем к exit syscall. Теперь это был бы минимальный минимум для достижения того же самого.


Подвести итоги

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

Отвечая на ваш основной вопрос:

Я плачу за то, что я не ем?

В этом конкретном случае да. Вы не пользуетесь чем-либо, что C++ может предложить больше, чем C, но это просто потому, что в этом простом кодексе, который C++ может вам помочь, ничего не может быть: это так просто, что вам действительно не нужно [ CN10].


О, и еще одна вещь!

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

C:

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main(void) {
    int i, n, *arr;

    printf("How many integers do you want to input? ");
    scanf("%d", &n);

    arr = malloc(sizeof(int) * n);

    for (i = 0; i < n; i++) {
        printf("Index %d: ", i);
        scanf("%d", &arr[i]);
    }

    qsort(arr, n, sizeof(int), cmp)

    puts("Here are your numbers, ordered:");

    for (i = 0; i < n; i++)
        printf("%d\n", arr[i]);

    free(arr);

    return 0;
}

C++:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void) {
    int n;

    cout << "How many integers do you want to input? ";
    cin >> n;

    vector<int> vec(n);

    for (int i = 0; i < vec.size(); i++) {
        cout << "Index " << i << ": ";
        cin >> vec[i];
    }

    sort(vec.begin(), vec.end());

    cout << "Here are your numbers:" << endl;

    for (int item : vec)
        cout << item << endl;

    return 0;
}

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

Ответ 8

Есть несколько заблуждений для начала. Во-первых, программа C++ не приводит к 22 инструкциям, это больше похоже на 22 000 из них (я вытащил это число из своей шляпы, но это примерно на стадионе). Кроме того, код C также не приводит к 9 инструкциям. Это только те, которые вы видите.

Что делает C-код, после того, как вы делаете много вещей, которые вы не видите, он вызывает функцию из CRT (которая обычно, но необязательно присутствует как shared lib), а затем не проверяет возвращаемое значение или дескриптор ошибок и сбоев. В зависимости от параметров компилятора и оптимизации он даже не вызывает вызов printf но puts или что-то еще более примитивное.
Вы могли бы написать более или менее одну и ту же программу (за исключением некоторых невидимых функций инициализации) в C++, если бы вы только называли эту же функцию одинаково. Или, если вы хотите быть супер-правильным, ту же самую функцию с префиксом std::.

Соответствующий C++ код на самом деле не является тем же самым. В то время как весь <iostream> хорошо известен как толстая уродливая свинья, которая добавляет огромные накладные расходы для небольших программ (в "реальной" программе вы действительно этого не замечаете), несколько более справедливая интерпретация заключается в том, что она делает очень много вещей, которые вы не видите и которые просто работают. Включая, но не ограничиваясь этим, магическое форматирование практически любых случайных вещей, включая различные форматы чисел и локалей, а также многое, буферизацию и правильную обработку ошибок. Обработка ошибок? Ну да, угадайте, что, вывод строки может действительно не работать, и в отличие от программы C программа C++ не будет игнорировать это молча. Учитывая то, что std::ostream делает под капотом, и, не зная об этом, он на самом деле довольно легкий. Не то, что я использую его, потому что я ненавижу синтаксис потока со страстью. Но все же, это довольно здорово, если вы считаете, что он делает.

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

Если вместо fire-and-forget-hope-for-the-best вы хотите написать код C, который является правильным (т.е. вы фактически проверяете наличие ошибок, и программа ведет себя корректно в присутствии ошибок), то разница является предельной, если существует.

Ответ 9

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

<Iomanip>

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

Предположим, вы хотите распечатать строку с 8-значным нулевым заполненным шестнадцатеричным беззнаковым int, за которым следует пробел, за которым следует двойной с 3 десятичными знаками. С помощью <cstdio> вы можете прочитать краткую строку формата. С помощью <ostream> вы должны сохранить старое состояние, установить выравнивание вправо, задать символ заполнения, установить ширину заливки, установить базу в шестнадцатеричный, вывести целое число, восстановить сохраненное состояние (иначе ваше целочисленное форматирование загрязнит ваше форматирование поплавка), вывести пространство, установить нотацию на фиксированную, установить точность, вывести двойную и новую строку, а затем восстановить старое форматирование.

// <cstdio>
std::printf( "%08x %.3lf\n", ival, fval );

// <ostream> & <iomanip>
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);

Перегрузка оператора

<iostream> является <iostream> плаката о том, как не использовать перегрузку оператора:

std::cout << 2 << 3 && 0 << 5;

Спектакль

std::cout в несколько раз медленнее printf(). Необузданный футурит и виртуальная отправка действительно сказываются.

Безопасность резьбы

Оба <cstdio> и <iostream> являются потокобезопасными, поскольку каждый вызов функции является атомарным. Но printf() получает намного больше за каждый звонок. Если вы запустите следующую программу с <cstdio>, вы увидите только строку f. Если вы используете <iostream> на многоядерной машине, вы, скорее всего, увидите что-то еще.

// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp

#define USE_STREAM 1
#define REPS 50
#define THREADS 10

#include <thread>
#include <vector>

#if USE_STREAM
    #include <iostream>
#else
    #include <cstdio>
#endif

void task()
{
    for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
        std::cout << std::hex << 15 << std::dec;
#else
        std::printf ( "%x", 15);
#endif

}

int main()
{
    auto threads = std::vector<std::thread> {};
    for ( int i = 0; i < THREADS; ++i )
        threads.emplace_back(task);

    for ( auto & t : threads )
        t.join();

#if USE_STREAM
        std::cout << "\n<iostream>\n";
#else
        std::printf ( "\n<cstdio>\n" );
#endif
}

Ответьте на этот пример, что большинство людей упражняются в дисциплине, чтобы никогда не писать в один файловый дескриптор из нескольких потоков. Ну, в этом случае вам нужно будет заметить, что <iostream> поможет захватить блокировку для каждого << и каждого >>. Если в <cstdio>, вы не будете блокировать так часто, и у вас даже есть возможность не блокировать.

<iostream> расходует больше блокировок для достижения менее согласованного результата.

Ответ 10

В дополнение к тому, что говорили все остальные ответы,
существует также тот факт, что std::endl не совпадает с '\n'.

Это, к сожалению, распространенное заблуждение. std::endl не означает "новая строка",
это означает "распечатать новую строку, а затем очистить поток ". Промывка не дешевая!

Полностью игнорируя различия между printf и std::cout на мгновение, чтобы быть функционально эквивалентным вашему примеру, ваш пример C++ должен выглядеть следующим образом:

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    return 0;
}

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

С

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    fflush(stdout);
    return 0;
}

C++

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    std::cout << std::flush;
    return 0;
}

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

Ответ 11

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

Известно, что в C++ вы платите за то, что едите.

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

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

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

Ответ 12

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

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

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

Ответ 13

Как вы видели в других ответах, вы платите, когда вы связываете в общих библиотеках и вызываете сложные конструкторы. Здесь нет особого вопроса, более серьезного. Я укажу некоторые реальные аспекты:

  1. У Барне был основной принцип проектирования, чтобы никогда не позволять эффективности быть причиной для пребывания на C, а не C++. Тем не менее, нужно быть осторожным, чтобы получить эту эффективность, и иногда есть эффективность, которая всегда срабатывала, но не была "технически" в спецификации C. Например, расположение битовых полей не было задано.

  2. Попробуйте взглянуть через ostream. О, мой бог, раздутый! Я не удивлюсь, если найду там симулятор полета. Даже stdlib printf() обычно работает около 50K. Это не ленивые программисты: половина размера printf была связана с аргументами непрямой точности, которые большинство людей никогда не использует. Почти всякая сильно ограниченная библиотека процессоров создает собственный код вывода вместо printf.

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

  4. Люди по-прежнему пишут ANSI C, хотя редко K & R C. Мой опыт заключается в том, что мы всегда компилируем его с помощью компилятора C++, используя несколько настроек конфигурации, чтобы ограничить перетаскивание. Хорошие аргументы для других языков: Go удаляет полиморфные служебные данные и сумасшедший препроцессор; были хорошие аргументы в пользу более удобной упаковки полей и макета памяти. IMHO Я думаю, что любой дизайн языка должен начинаться с перечисления целей, как Zen of Python.

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

Нет ответа. Ответ не будет. Это ответ.