Как стать автором
Обновить

Комментарии 114

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

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

А должен так же быстро или даже быстрее?

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

Это вы еще раст не собирали))

Я какое-то вермя работал в одном кабинете с разработчиками аппаратуры на VHDL и Verilog. Они снисходительно смеялись когда я жаловался на скорость компиляции плюсов.

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

Не согласен на счёт всех. Как минимум, есть pascal/delphi и golang, которые компилятся очень быстро. А компиляция крупных проектов на плюсах может занимать длительное время даже с выключенной оптимизацией. А с учётом тьюринг-полных шаблонов она вообще может длиться вечно. :)

А есть ли там LTO, whole program optimization? Мне кажется, что нет.

Например, скорость компиляции MSVC с оптимизациями может быть на порядок, а то и два дольше.

На счёт паскаля не знаю, в го, вроде, нет LTO. Но опять же, плюсы и без оптимизаций дольше компилятся, чем го с оптимизациями. Даже с precompiled headers. Так или иначе, долгая компиляция си++ это недостаток, достойный упоминания.


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


P.S. И не дай бог поменять бранч… Иначе проект будет пересобираться заново, что займёт уже не 1-2 минуты, а все 20.

На руках сейчас проект, где изменение одного CPP файла приводит к компиляции+линковке минуты на 4. Если тронуть хедер, то можно идти пить чай

И это всё на довольно мощной рабочей станции, не ноут

Ноут за 30к, купленный в 2018 году - полная сборка хромиума летом 2021 заняла почти сутки. 23 часа с копейками :D

Не знаю, следует ли это записывать в недостатки плюсов (в любом случае, объем кода просто монструозный, на любом языке, кмк, сборка длилась бы десятки часов, не знаю), просто делюсь фан фактом из жизни :D

Ну то, что это заняло меньше суток, это весьма обнадёживающе.))


Я ради интереса собирал kubernetes на raspberry pi 4 с 4гб памяти — чистая сборка занимает 25 минут. Наверняка у него кодовая база гораздо меньше. Допустим, судя по быстрому гуглению оценок cloc, в 25 раз меньше. В этом случае, сборка бы сопоставимого с хромиумом проекта на го заняла бы на малинке 10 часов. :)

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

Да понятное дело. Такие вещи сравнивать вообще дело неблагодарное, просто интересно было хотя бы грубо прикинуть.

Главная проблема в том, что можно просто наугад выбрать 10 любых C++ проектов на Github, сделать git clone, и все 10 откажутся собираться стандартной связкой ./configure с make.
Ни в одном другом инструментарии нет таких чудовищных проблем со сборкой проекта, как в тулчейне C++. К каждому проекту всегда прикладывают коротенькую инструкцию, странички этак на четыре, с описанием того, как всё это собирать. А если мы хотим из-под Linux собрать что-то под винду, то сложность сборки вообще устремляется в небеса. Для сборки ряда крупных проектов помимо собственно C++ вам потребуется доустановить Node.js, Perl, Python и Go, потому что процесс сборки использует скрипты на нескольких разных языках одновременно.
Отдельно доставляет крайне сложная и запутанная система ключей компилятора и линкера, которые взаимодействуют весьма непредсказуемым образом, а их подбор для успешной сборки чужого проекта — отдельный вид шаманства. Не удивлюсь, если окажется, что система ключей там уже давно Тьюринг-полная.

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

К счастью, современные пакетные менеджеры(более-менее уверенно могу говорить только о vcpkg и conan, но они не единственные) весьма успешно решают проблемы с прикручиванием к своему проекту библиотек.

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

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

Поэтому весь fortran и c/c++ юзают через обёртки для python, что бы не заниматься анонизмом нетрадиционными сборками стоя и в гамаке.

Еще недостаток - трудность и неудобство отладки.

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

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

В основном из-за прямого доступа к памяти. По сравнению например с python

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

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

Забавно, что почти все подчёркнутые им плюсы я считаю минусами, а названным им минусам наоборот рад. Да, когда-то давно в первые пару лет использования C/C++ я тоже злился от разделения на cpp- и h-файлы. Но затем понял фишку, постиг дзен и поражался, какой же я был наивный дурак я был (чисто в духе парадокса Даннинга-Крюгера). А здесь человек 15 лет (!!!) пишет на C++, но видимо до сих пор не вдупляет в то, зачем нужны прототипы, в чем разница между declaration и definition, и почему заголовочные файлы это благо.

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

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

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

Но теперь это — атавизм

Что поменялось «теперь» по сравнению с временами, когда язык создавался? Люди стали ходить на голове? Число Пи достигло значения 8? Поменялась размерность пространства-времени?

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

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

Что поменялось за это время: выросла кодовая база, число зависимостей. Пока программы состояли из пары десятков файлов и умещались в 640 кб памяти, всё было в порядке. Сейчас программы огромные и сложность компиляции из-за парсинга кучи вырастает экспонециально.

Не глупые люди придумали скрывать детали реализации.
Так появились интерфейс и его реализация, которая может меняться не изменяя интерфейса. И интерфейс реализации складывают в заголовки, а реализацию по c/cc/cxx/c++/cpp/… файлам (в идеале).
Но можно и микроскопом забивать гвозди — вот именно так и появились header only c++ библиотеки.

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

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

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

НЛО прилетело и опубликовало эту надпись здесь

Попробуйте писать на C# и увидите, насколько просто и гибче скрываются детали реализации )

Интересно, зачем тогда F# изобретали если с C# всё так гибко шо пипец.

ps: Если хочется новых ощущений надо пробывать erlang

С F# не работал, но предполагаю, что у него своя нишша. И вопрос не в том, что в C# все гибко, а в том, что в нем ты просто делаешь, что тебе надо, без раздумий о том, какие костыли на этот раз запилить, чтобы оно хоть немного работало. А не как в C++: атрибуты и прочая метаинформация? ПФ... Да кому это нужно! Рефлексия? На чайнике рефлексия не нужна, а значит она не нужна вовсе! Серализация? Да без проблем! Сейчас расчехлим пару сотню костылей, которые уже через неделю все забудут как поддерживать и все ) Сеть? А что, ей кто то пользуется? Придётся поискать нужные библиотеки и ещё несколько часов (если не дней) мудохаться с их подключением!

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

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

Грубо говоря, когда в обществе долгое время существовал консенсус, что убивать не хорошо, что справедливость это благо, а несправедливость это плохо, и вдруг приходит молодое поколение и внезапно ставит вопрос: «а почему собственно убивать не хорошо?».

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

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

Так вот, если у вас 190 штук cpp-файлов, и во всех вы используете структуры VECTOR3D, WAVEHDR, ANOTHERSTRUCT, то в каждом cpp-файле придется иметь объявление этих структур. 190 × 3 объявлений.

И вот здесь, чтобы не плодить 190 копий объявления трёх структур, чтобы уйти от WET-антипаттерна и использовать DRY-принцип, на сцену выходит препроцессорая фича #include, позволяющая повторяющиеся вещи вынести в один файл и избавиться от повторений, за исключением того, что сама директива #include и имя файла, указанное в ней, таки будет повторяться много раз.

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

И вот приходит человек и говорит, что h-файлы, которые созданы чтобы помогать бороться с дублированием, заставляют его писать что-то дважды!

И дело-то видимо не в том, что человек против идеи includeинга файлов на стадии препроцессинга или не понимает как эти пользоваться. А дело скорее всего в том, что человек против идеи прототипов функций, а также он испорчен и избалован IDE, которая прячет процесс сборки и представляет его пользователю как нечто непрозрачное, и человек перестает думать отдельно о линковке, отдельно о компиляции каждого отдельного исходного файла независимо от остальных, а начинает считать, что есть некий черный ящик под названием «компилятор C++», который как-то там вызывается один раз сразу на весь проект, видит сразу весь проект, обрабатывает весь проект и выплёвывает готовый исполняемый файл, а раз так, ну уж наверное он может в одном cpp-файле увидеть определение структуры или функции, а встретив упоминание чего-то такого в другом cpp-файле, который ни сном ни духом про первый файл, самостоятельно догадаться, что вот именно из того файла сущности здесь и упоминаются.

Для примера, тот же C#, так же позволяет писать код и разделять его на разные файлы, но не заставляет тебя самостоятельно писать заголовки. Компилятор сам генерирует все необходимые метаданные. Генерация кода автоматически как раз соответствует принципу DRY.

Понятно. Это что-то вроде желания, будь вы из сферы машиностроения, забыть ненавистное вам оформление проектной документации, а сразу вы тачивать детали «из головы», но чтобы потом по эти деталям проектная документация (чертежи, 3D-модели, спецификации) сами генерировались.

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

Точно так же в других языках программирования есть такие абстракции: interface в C#, например.

Кстати, с h-файлами есть недостаток: вы не можете объявить только публичную часть класса. Вам придётся в h-файле объявить и все приватные поля, и все приватные методы.

Совершенно не обязательно
// a.h
#pragma once
struct A {
  static A* create();
  virtual int fn()=0;
  virtual ~A() {}
};

// a.cpp
#include "a.h"
struct A1 : A {
  int private_variable;
  int fn() { return private_variable; }
};
A* A::create() {
  A1 *a=new A1();
  a->private_variable=49;
  return a;
}

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

В C#, например, так делать можно:

// Interface part
    
partial class A1
{
    public partial int fn();
}
   
// Implementation part

partial class A1
{
    int private_variable;
    public partial int fn() { return private_variable; }
}
Вы на пустом месте зачем-то завели виртуальный класс

Так это как раз недостатки C++, в чистом C такой фигни не было:
// a.h
typedef struct A A;
int  A_init(A** a);
void A_done(A **a);
int  A_fn(A* a);

// a.c
#include <stdlib.h>
struct A {
  int private_variable;
};
int A_init(A** res) {
  A *a=(A*)malloc(sizeof(A)); if (!a) return 1;
  a->private_variable=49;
  *res=a;
  return 0;
}
int A_fn(A* a) {
  return a->private_variable;
}
void A_done(A **a) {
  if (*a) { free((void*)*a); *a=0; }
}

Так это как раз недостатки C++

Так именно это мы и обсуждаем здесь.

Кстати, с h-файлами есть недостаток: вы не можете объявить только публичную часть класса. Вам придётся в h-файле объявить и все приватные поля, и все приватные методы.

Потому что в момент, скажем, создания экземпляра класса на стеке компилятор должен знать его размер. Откуда он будет знать размер, если полного описания класса нет? Создать экземпляр класса of incomplete type не получится. Плюс создавать экземпляр класса компилятор должен уметь даже если у него нет ничего, кроме декларации в h-файле и уже собранной библиотеки с методами класса.

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

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

Компилятор вполне может узнать эту информацию из соседних cpp-файлов.

Так их вполне может не быть. Разработчик библиотеки свободно может выбрать ее распространение только в бинарном виде (.lib/.a/.dll/.so). А создавать экземпляр класса нужно уметь вне зависимости от способа распространения библиотеки.

Бинарные библиотеки (.a/.dll/.so) нельзя использовать из C++ напрямую, для этого нужен клей в виде .lib-файла.

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

Бинарные библиотеки (.a/.dll/.so) нельзя использовать из C++ напрямую, для этого нужен клей в виде .lib-файла.

Что, простите? Заголовочный файл (lib.h):

#pragma once

#include <string>

class C
{
public:
    C();
    const std::string &str();
private:
    std::string s;
};

Реализация (lib.cpp):

#include "lib.h"

C::C()
    : s("HELLO FROM LIB")
{
}

const std::string &C::str()
{
    return s;
}

Вызывающий код (main.cpp):

#include <iostream>

#include "lib.h"

int main()
{
    C c;

    std::cout << c.str() << std::endl;
}

Собираем:

$ g++ -c -o lib.o lib.cpp
$ ar rcs lib.a lib.o
$ g++ -o main main.cpp lib.a

Запускаем:

$ ./main
HELLO FROM LIB

Никаких проблем.

P.S. то же самое с динамической библиотекой:

$ g++ -fpic -shared -o lib.so lib.cpp
$ g++ -o main main.cpp lib.so
$ export LD_LIBRARY_PATH=.; ./main
HELLO FROM LIB

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

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

А теперь покажите как вы подключаете opengl 4.6

Зачем? В общем случае тезис "бинарные библиотеки нельзя использовать из C++ напрямую" - ложный, и я это только что наглядно продемонстрировал. I'm not here for your entertainment (C).

Что, простите?

Вы в своём примере подключаете не бинарный файл, а промежуточное представление (.lib). В отличие от .so/.a/.dll, вы не можете сделать LoadLibrary/dlopen и вызвать код оттуда.

P.S. то же самое с динамической библиотекой:

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

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

Именно так.

Вы в своём примере подключаете не бинарный файл, а промежуточное представление (.lib). В отличие от .so/.a/.dll, вы не можете сделать LoadLibrary/dlopen и вызвать код оттуда.

Я подключаю не "промежуточное представление", с чего вы это взяли? Нет там никакого "промежуточного представления". Я подключаю статическую (.a) и динамическую (.so) библиотеку в том виде, в котором они будут распространяться. Для этого достаточно иметь собственно библиотеку (один файл .a или .so) и заголовочный файл, больше ничего не требуется. Динамическая библиотека у меня загружается автоматически при запуске бинаря, а не вручную через dlopen(), это так, но это связано не с наличием или отсутствием некоего "промежуточного представления", а с тем, что dlsym() не совместим с сущностью, называемой в C++ "pointer to member function". Статический метод класса, однако, вызвать таким образом будет вполне возможно. Под виндой, кстати, вполне возможно что удастся вызвать даже и нестатический метод, но я давно не тренировался, так что не уверен на 100%.

Именно так.

Обратитесь в комитет с proposal.

Я подключаю статическую (.a)

Да, перепутал. Тогда это то же самое, что и (.lib) — промежуточная форма.

Я могу эту .a/.lib подключить из C# или Go?

Не знаю, что вы называете "промежуточной формой". .a - это архив из объектных файлов (.o), стандартный формат статической библиотеки в *nix.

Я могу эту .a/.lib подключить из C# или Go?

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

Не знаю, что вы называете "промежуточной формой"

Очевидно, то, что нельзя просто взять и запустить.

Объектный модуль (.o) — это не бинарь, это промежуточная форма (compilation unit).

.a - это архив из объектных файлов (.o),

Ну да, архив из промежуточных форм.

Кстати, его конкретное представление стандартом не описывается.

Очевидно, то, что нельзя просто взять и запустить.

Динамическую библиотеку (.so или .dll) тоже нельзя просто взять и запустить, это тоже "промежуточная форма" или нет? Хочу разобраться в терминологии. В любом случае, для подключения .so .a не нужен, нужен только .so и .h.

Кстати, его конкретное представление стандартом не описывается.

Конечно, нет. Он же не зависит от конкретного языка.

Динамическую библиотеку (.so или .dll) тоже нельзя просто взять и запустить, это тоже "промежуточная форма" или нет?

Можно. dlopen, dlsym. Отличие .so/.dll от .out/.exe в том, что в последних есть точка входа для прямого запуска.

В любом случае, для подключения .so .a не нужен, нужен только .so и .h.

А под виндой для подключения .dll нужен и .lib, и .h.

Конечно, нет. Он же не зависит от конкретного языка.

Он зависит от принятых соглашений. Например, ничто не мешает создать свои компилятор и линкер со своим представлением .o и .a. Ну не будет оно совместимо с экосистемой gcc/ld, ничего страшного. Просто никто этим не будет пользоваться.

Можно. dlopendlsym

Видимо, у нас разные понятия о "взять и запустить".

А под виндой для подключения .dll нужен и .lib, и .h.

Да, под виндой нужна еще дополнительная прослойка в виде import library. Но это не имеет отношения к C++ как таковому, просто в Windows так принято делать implicit linking с DLL. В системах с ELF это не требуется.

Он зависит от принятых соглашений. Например, ничто не мешает создать свои компилятор и линкер со своим представлением .o и .a. Ну не будет оно совместимо с экосистемой gcc/ld, ничего страшного. Просто никто этим не будет пользоваться.

Под большинством современных *nix .o - это ELF. Такой же, как исполняемые файлы и динамические библиотеки. А то, что "никто не мешает, но никто не будет пользоваться" - это правда. Поэтому у меня и сарказм на тему ваших представлений о том, где C++ компилятор должен находить данные о размере объектов.

Видимо, у нас разные понятия о "взять и запустить".

Да. Взять и запустить код из файла. В случае .lib/.a для этого обязательно нужен определённый компилятор, понимающий формат этого файла. В случае .dll/.so — API операционной системы. Кстати, .out/.exe тоже через вызовы функций надо запускать (CreateProcess, exec).

Но это не имеет отношения к C++ как таковому,

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

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

Да, обратная совместимость, которая вставляет палки в колёса. Но ничто не мешало просто расширить метаданные, не поломав совместимость: подцепляем из C — размера типа не видим, подцепляем из C++ — видим.

В случае .lib/.a для этого обязательно нужен определённый компилятор, понимающий формат этого файла.

Как я уже выше сказал, под большинством *nix .a - это архив объектных файлов (.o) стандартного формата (ELF), не зависящего от компилятора. ld способен собирать из них исполняемый файл без посторонней помощи, ничего не зная о компиляторе, который их сделал. При большом желании их можно даже запустить вручную.

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

Так dll'ки же не только C++ может создавать. Почему то, как их создавать, должно быть описано в стандарте? Стандарт ортогонален конкретному формату исполняемых файлов и динамических библиотек на конкретной платформе.

Да, обратная совместимость, которая вставляет палки в колёса.

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

Как я уже выше сказал, под большинством *nix .a - это архив объектных файлов (.o) стандартного формата (ELF), не зависящего от компилятора.

Ну да, так сложились звёзды, что компилятор C++ в Unix-подобных ОС генерит файлы стандартного формата.

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

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

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

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

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

Ммм, а зачем? Чтобы заведомо вынуждать генерить файлы нестандартного и несовместимого ни с чем формата, с которыми не сможет работать стандартный компоновщик? И все ради того, чтобы .h-файл не прикладывать? И что бы мы выгадали? Вместо .so и .h был бы .so и какой-нибудь .pkg (а еще веселее - И .so И .h И .pkg)? Шило на мыло.

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

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

И все ради того, чтобы .h-файл не прикладывать?

Нет. Всё ради того, чтобы не выкладывать кишки класса наружу. То есть чтобы в .h-файле были только публичные методы и поля.

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

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

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

Мне тоже приходилось это делать, и не раз. Но я же не жалуюсь.

Не заменить формат, а расширить его.

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

А теперь представьте, что есть языки, где подключение нового класса не равно подключению файла и где нет разделения на заголовочные файлы и файлы с исходным кодом, для описания интерфейсов используются именно интерфейсы и при этом все компилируется в разы быстрее, чем в C++, а compile-time ошибки всегда указывают на строку, где они произошли, а не на объектный файл )

НЛО прилетело и опубликовало эту надпись здесь

А главным образом в том, что каждый cpp-файл можно компилировать отдельно и в отрыве от всех остальных

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

Например, скомпилировать один файл на 10000 строк займёт значительно меньше времени, чем 100 файлов по 100 строк. Поэтому приходится тесно связанные вещи пихать в один файл внавал.

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

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

Так вот, если у вас 190 штук cpp-файлов, и во всех вы используете структуры VECTOR3D, WAVEHDR, ANOTHERSTRUCT, то в каждом cpp-файле придется иметь объявление этих структур. 190 × 3 объявлений.

Да, это особенность компиляции программ C/C++: мы компилируем каждый файл в отрыве от остальных, а потом пытаемся собрать воедино из объектников.

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

А дело скорее всего в том, что человек против идеи прототипов функций

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

Написал не менее длинный ответ на этот коммент, и в последний момент он канул в небытие. Слава новому WYSIWYG-редактору Хабра!

Второй раз набирать нет желания: я вообще-то с телефона набирал, так как лежу с температурой 39, палец и так отваливается. Может быть потом

Буква «О» в аббревиатуре «SOLID» постоянно пролетает мимо? Да и другие буквы тоже как-то не выживают в этом гипотетическом проекте, где 100 файлов зависят от одного, который ежедневно и ежечасно правят.
Например, скомпилировать один файл на 10000 строк займёт значительно меньше времени, чем 100 файлов по 100 строк. Поэтому приходится тесно связанные вещи пихать в один файл внавал.
main.cpp:
#include "1.cpp"
#include "2.cpp"
....
?
Жить надоело? :)))

Да. так делать можно. Но неудобно: надо вручную следить за включением каждого файла.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

И вот здесь, чтобы не плодить 190 копий объявления трёх структур, чтобы уйти от WET-антипаттерна и использовать DRY-принцип, на сцену выходит препроцессорая фича #include, позволяющая повторяющиеся вещи вынести в один файл и избавиться от повторений, за исключением того, что сама директива #include и имя файла, указанное в ней, таки будет повторяться много раз.

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

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

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

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

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

Но круто, что кого-то в 2022 может восхитить код, собирающий один файл из нескольких.

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

М-м-м. Это как в Java/Kotlin, где ты должен сначала трудолюбиво разложить файлы по папочкам, потом в начале каждого файла не забыть трудолюбиво написать package com.horns.and.hooves.api.shit.auth? Крутяк. А потом, если Horns & Hooves потребуется переименовать в Hooves & Horns, процесс надо будет повторить - ну или понадеяться на какую-нибудь киллер фичу очередной мега IDE по решению проблемы, которой бы не было, если бы в язык не была добавлена столь по-дурацки спроектированная "особенность". Запах современного языка настолько силен, что глаза вытекают.

Всего этого в с++ нет, не предвидится в ближайшие несколько лет и можно только надеяться

что в C++ такого не будет никогда.

НЛО прилетело и опубликовало эту надпись здесь

Даже если и ничем (хотя, конечно, есть чем - после перемещения в другой каталог хедеры обычно править не требуется) - в чем "модерновость"-то тогда? :)

НЛО прилетело и опубликовало эту надпись здесь

Ну вообще-то требуется — нужно обновить include guard.

Не нужно :)

В отсутствии прпрепроцессора.

Утверждение, что наличие (ну или отсутствие) препроцессора как-то доказывает отсталость (или модерновость), несколько голословное на мой вкус :) Отдаёт эдакой вкусовщиной, знаете ли.

НЛО прилетело и опубликовало эту надпись здесь

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

#pragma once :) Да, это нестандартно, это имеет свои минусы, но де-факто это есть.

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

Знаете, как говорят - не смотрите на это как на проблему, смотрите, как на возможность :)

НЛО прилетело и опубликовало эту надпись здесь

И возможность чего это?

Возможность осуществить тупую подстановку текста (и игнорировать часть текста в зависимости от текста, который был тупо подставлен перед этим), конечно же! В Java/Kotlin такое возможно? Нет :)

НЛО прилетело и опубликовало эту надпись здесь

А зачем это?

Пригодится.

Ими, к счастью, мир не ограничивается.

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

НЛО прилетело и опубликовало эту надпись здесь

Нужно использовать `#pragma once`. Иначе противоречие: мы поддерживаем какие-то старые подходы в языке, тем самым потокаем системам которые древнее мамонтов, а потом требуем чего-то современного?

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

Тем временем в нормальных языках начиная ну с Turbo Pascal и далее везде эти структуры скомпилятся один раз, и все остальные единицы трансляции будут видеть их в виде бинарных метаданных

Поэтому существуют многочисленные «лагеря несогласных» с «де-факто стандартным» CMake: Make/Ninja/Meson/Build2 и другие. До сих пор нет по-настоящему удобной интеграции с IDE (та же VisualStudio).

Довелось поработать с CMake. Но относительно недавно (чуть более полугода назад) наткнулся на https://premake.github.io/. На мой взгляд очень простая, но в то же время очень мощная система генерации make-файлов. Очень интересно узнать, использует ли кто-то данный инструмент у себя в средних и больших проектах ?

Друзья используют, отзывы очень хорошие.

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

Эк вы безапелляционно рассуждаете. Прямо как в известном анекдоте:

— Запомни, сынок, умный человек всегда во всем сомневается. Только дурак может быть полностью уверенным в чем-то.

— Ты уверен в этом, папа?

— Абсолютно, сынок!

(Особенно смешно читать было про заголовочные файлы которые уменьшают дублирование кода)

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

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

А просветите тогда, как можно модно и молодежно использовать #include ?

Я же написал выше:

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

Использование #include не требует от каждого из этих компиляторов уметь парсить код всех остальных для того, чтобы выделить экспортируемые сущности, это раз (это уж не говоря о случае, когда библиотека доступна в бинарном виде). Во-вторых, в заголовочном файле допустима условная компиляция, которая позволяет "спрятать" те интерфейсы, которые компилятор на требуемом языке может не понять, и "показать" те, которые он понять может. Это как минимум. Вот в C++20 добавляют модули, но они не годятся для такого случая, так же, как не годятся и ObjC-специфичные вещи типа@import. А #include годится.

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

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

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

У c++ есть один недостаток - я запарился его учить.

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

Напоминает древний культ

У цитате Страуструпа:

Garbage is generated by C++ programmers instead.

Обычно, я не читаю книги по программированию - предпочитаю ковыряться в чужом коде.
Но, эта книга стала исключением - "C++ concurrency in action"
Я вернулся (после C#, Kotlin) к C++ и обнаружил, что в "мое отсутствие" в нем всё кардинально изменилось.

От Borland была VCL (Visual Components Library), а не VLC.

поправили, спасибо за внимательность!

Продолжая тему C++03: C++(C++03) был долгое время плохо оптимизирован, не смотря на заявления Bjarne Stroustrup о Zero-overhead principle.

В стандартной библиотеке C++ даже в 2020 не было одной важной сортировки. Даже у Кнута в первых редакциях она была!

Я лично пользуюсь стронними библиотеками т.к. других вариантов C++ мне не даёт.

Zero-overhead principle — это рекламный трюк не более. Ничего бесплатного не бывает. Зато им можно громко размахивать вместе со стандартом и тыкать в морду лица оппонентам.

Проблема cin и cout (правдая было или не было автор уже не помнит). Решение

Я тупо переходил на C и Java в таких случаях.

Т.е. то что printf и scanf можно использовать из С++ вы не знали, и не знаете до сих пор? :) Про то что С++ позволяет вам напрямую работать с файлами если вам действительно нужен быстрый вывод, я уже молчу. Но вообще msvc известен своей криворукостью.

НЛО прилетело и опубликовало эту надпись здесь

Но это не является основной проблемой, там в iostream есть похуже тормоза, какие то локи, для флоатов локали (?), и под виндой их (было?) больше. Вообщем каким-то образом автор пришёл к сишным функциям 15 лет назад =)

НЛО прилетело и опубликовало эту надпись здесь

так это были строки или числа?

НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий