Подтвердить что ты не робот

Могу ли я написать код на С++ без заголовков (объявления повторяющихся функций)?

Есть ли способ не писать записи функций дважды (заголовки) и по-прежнему сохранять ту же масштабируемость при компиляции, ясности в отладке и гибкости в дизайне при программировании на С++?

4b9b3361

Ответ 1

Используйте lzz. Он принимает один файл и автоматически создает .h и .cpp для вас со всеми объявлениями/определениями в нужном месте.

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

Обновление 150120:

Новый синтаксис С++ '11/14 может использоваться только в телах функций lzz.

Ответ 2

Я чувствовал то же самое, когда начал писать C, поэтому я также изучил это. Ответ в том, что да, это возможно и нет, вы не хотите.

Сначала да.

В GCC вы можете сделать это:

// foo.cph

void foo();

#if __INCLUDE_LEVEL__ == 0
void foo() {
   printf("Hello World!\n");
}
#endif

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

Затем с no:

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

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

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

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

Ответ 3

Извините, но нет такой вещи, как "лучшая практика" для устранения заголовков на С++: это плохая идея, период. Если вы так сильно их ненавидите, у вас есть три варианта:

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

Ответ 4

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

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

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

Ответ 5

В своей статье Простая поддержка дизайна по контракту на С++, Педро Геррейро заявил:

Обычно класс С++ состоит из двух файлы: файл заголовка и файл определения. Где мы должны писать утверждения: в файле заголовка, потому что утверждения являются спецификацией? Или в файле определения, поскольку они исполняются? Или в обоих, работает риск несогласованности (и дублирующая работа)? Мы рекомендуем, вместо этого мы оставляем традиционный стиль, и покончить с файл определения, используя только заголовочный файл, как если бы все функции были определенный inline, очень похожий на Java и Эйфель.

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

Это был 2001 год. Я согласился. Сейчас 2009 год, и до сих пор нет "среды разработки, которая скрывает от нас, что позволяет нам сосредоточиться на наших классах". Вместо этого, длинные времена компиляции являются нормой.

К счастью, теперь у нас есть С#, где мы не можем использовать файлы заголовков... и длительные сроки компиляции:)


Примечание.. Теперь ссылка выше мертва. Это полная ссылка на публикацию, как показано в разделе Publications автора веб-сайт:

Педро Гуэррейро, Простая поддержка дизайна по контракту на С++, TOOLS USA 2001, Proceedings, pages 24-34, IEEE, 2001.

Ответ 6

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

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

Ответ 7

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

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

Если вы думаете о системе, подобной С# или Java, это невозможно в С++.

Ответ 8

Это программное обеспечение для создания файлов заголовков. Я никогда не использовал его, но, возможно, стоит заглянуть в него. Например, проверьте mkhdr! Он предположительно сканирует файлы C и С++ и генерирует соответствующие файлы заголовков.

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

Ответ 9

Собственно... Вы можете написать всю реализацию в файле. Шаблонные классы определены в файле заголовка без файла cpp.

Вы также можете сохранить все необходимые расширения. Затем в операторах #include вы должны включить свой файл.

/* mycode.cpp */
#pragma once
#include <iostreams.h>

class myclass {
public:
  myclass();

  dothing();
};

myclass::myclass() { }
myclass::dothing()
{
  // code
}

Затем в другом файле

/* myothercode.cpp */
#pragma once
#include "mycode.cpp"

int main() {
   myclass A;
   A.dothing();
   return 0;
}

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

Ответ 10

Вы можете избегать заголовков. Полностью. Но я не рекомендую это.

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

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

Ответ 11

Чтобы предложить вариант на популярном ответе rix0rrr:

// foo.cph

#define INCLUDEMODE
#include "foo.cph"
#include "other.cph"
#undef INCLUDEMODE

void foo()
#if !defined(INCLUDEMODE)
{
   printf("Hello World!\n");
}
#else
;
#endif

void bar()
#if !defined(INCLUDEMODE)
{
    foo();
}
#else
;
#endif

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

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

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

Ответ 12

После прочтения всех других ответов, я нахожу это отсутствующим, что продолжается работа по добавлению поддержки модулей в стандарте С++. Это не будет сделано для С++ 0x, но намерение заключается в том, что он будет рассмотрен в более позднем техническом обзоре (а не в ожидании нового стандарта, который займет много времени).

Предложение, которое обсуждалось, N2073.

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

Ответ 13

Никто еще не упомянул Visual-Assist X в Visual Studio 2012.

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

  • "Создать декларацию" копирует объявление функции из текущей функции в файл .hpp.
  • "Refactor..Change signature" позволяет одновременно обновлять файлы .cpp и .h с помощью одной команды.
  • Alt-O позволяет вам мгновенно переключаться между файлами .cpp и .h.

Ответ 14

Насколько я знаю, нет. Заголовки являются неотъемлемой частью языка С++ как языка. Не забывайте, что декларация forward позволяет компилятору просто включать указатель функции на скомпилированный объект/функцию без необходимости включать всю функцию (которую вы можете обойти, объявив функцию inline (если компилятор чувствует себя так).

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

Ответ 15

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

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

Ответ 16

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

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

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

Многие люди, однако, не понимают, что С++ - это ТОЛЬКО язык, который имеет сильные и современные решения для проблем высокого и низкого уровня. Легко сказать, что вы должны пойти на другой язык с правильной системой отражения и сборки, но не имеет смысла, что мы должны жертвовать низкоуровневыми программными решениями с этим, и нам нужно усложнять ситуацию с использованием низкоуровневого языка с некоторым решением на основе виртуальной машины /JIT.

У меня есть эта идея в течение некоторого времени, что было бы самой крутой вещью на земле иметь "единичную" основанную на инструментах цепочку С++, похожую на ту, что в D. Проблема связана с кросс-платформенной частью: объектные файлы могут хранить любую информацию, никаких проблем с этим, но поскольку на окнах структура файловой структуры отличается от структуры ELF, было бы больно в попке реализовать кросс-платформенное решение для хранения и обработки единицы частичной компиляции.

Ответ 17

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

#include "MyModule.c"

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

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

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

  • Значительно меньше загрязнений файлов в исходном дереве.
  • Более быстрое время сборки. (Только один объектный файл создается компилятором, main.o)
  • Более простые файлы. (Только один объектный файл создается компилятором, main.o)
  • Не нужно "чистить". Каждая сборка "чиста".
  • Меньше кода плиты котла. Меньше кода = меньше потенциальных ошибок.

Я обнаружил, что Гиш (игра Cryptic Sea, Эдмунд Макмиллен) использовал вариацию этой техники в своем собственном исходном коде.

Ответ 18

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

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

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

Ответ 19

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

Ответ 20

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

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

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

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

Ответ 21

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

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

Ответ 22

Это было "возрождено" благодаря дубликату...

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

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

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

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

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

Идеальная система будет работать с

  • определение и декларация отдельного класса
  • Ясное связывание между этими двумя, чтобы компилятор знал, где объявляется объявление класса и его определение, и будет знать, какой размер класса.
  • Вы объявляете using class, а не предварительный процессор #include. Компилятор знает, где найти класс. После того, как вы выполнили "использование класса", вы можете использовать это имя класса без его квалификации.

Мне было бы интересно узнать, как это делает D.

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

Ответ 23

Вы также можете использовать только header file , вместо этого c++ file.

Просто добавьте header file, и вы не создадите c++ file, ссылаясь на него. Таким образом, вы непосредственно добавляете код в header, и поэтому можете использовать его как code file.

Это пример использования header file без c++ file :

#pragma once
#include <windows.h>

static class ConsoleUtils
{
public:
    static BOOL SetConsoleDisplayMode(HANDLE hOutputHandle, DWORD dwNewMode)
    {
        typedef BOOL(WINAPI *SCDMProc_t) (HANDLE, DWORD, LPDWORD);
        SCDMProc_t SetConsoleDisplayMode;
        HMODULE hKernel32;
        BOOL bFreeLib = FALSE, ret;
        const char KERNEL32_NAME[] = "kernel32.dll";
        hKernel32 = GetModuleHandleA(KERNEL32_NAME);
        if (hKernel32 == NULL)
        {
            hKernel32 = LoadLibraryA(KERNEL32_NAME);
            if (hKernel32 == NULL)
                return FALSE;
            bFreeLib = true;
        }
        SetConsoleDisplayMode = (SCDMProc_t)GetProcAddress(hKernel32, "SetConsoleDisplayMode");
        if (SetConsoleDisplayMode == NULL)
        {
            SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
            ret = FALSE;
        }
        else
        {
            DWORD tmp;
            ret = SetConsoleDisplayMode(hOutputHandle, dwNewMode, &tmp);
        }
        if (bFreeLib)
            FreeLibrary(hKernel32);
        return ret;
    }
};