При использовании модулей С++ есть ли причина отделять объявления функций (файлы .hpp) от их определений (файлы .cpp)?

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

// foo.h 
class Foo
{
    void bar();
};

и соответствующий файл .cpp содержит определение:

// foo.cpp
#include "foo.h"

void Foo::bar()
{
    // ...
}

Насколько мне известно, это делается для уменьшения времени компиляции и уменьшения зависимостей. . Когда будут использоваться модули, это все еще применимо? Было бы так же быстро иметь класс в одном файле с определениями, как это делают Java и C#? Если это так, будут ли нужны файлы .hpp и .cpp при использовании модулей?


person Phlox Midas    schedule 23.09.2016    source источник
comment
Как вы будете ссылаться на функцию? Если вы #include объедините заголовок и файл .cpp, вы будете многократно определять функции.   -  person Ant    schedule 28.04.2017


Ответы (4)


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

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

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

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

Foo module file

export module Foo;

import module Bar;

export namespace Test {
    class Foo {
    public:
        Bar createBar() {
            return Bar();
        }
    };
}

Bar module file

export module Bar;

import module Foo;

export namespace Test {
    class Bar {
    public:
        Foo createFoo() {
            return Foo();
        }
    };
}

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

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

person Qub1    schedule 19.07.2018
comment
Я никогда не считал круговые зависимости чем-то желательным. Скорее, я считал это проблемой дизайна. В этом смысле, это даже хорошая идея поддерживать это? - person BitTickler; 20.07.2018
comment
@BitTickler Я согласен с вами в том, что циклические зависимости могут указывать на проблему дизайна, но это не всегда так, а иногда просто трудно избежать циклических зависимостей. Рассмотрим сценарий, в котором у вас есть дерево узлов, где каждый узел хотел бы ссылаться на своего родителя и дочерние элементы. Это создает циклическую ссылку. Это справедливо практически для любых родительских ‹-› дочерних отношений, таких как, например, класс Player и Item, где класс Player ссылается на имеющиеся у него элементы, а класс Item ссылается на своего владельца. - person Qub1; 20.07.2018

Есть еще много причин для использования заголовочных файлов.

Простота совместного использования и понимания API объекта без просмотра основных деталей достаточно полезна, чтобы сохранить их. Это хороший 20-футовый вид объекта, по сути являющийся контуром.

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

Я не верю, что это возможно без заголовочных файлов.

person Ethan    schedule 24.03.2018

Существует хорошее обсуждение здесь, объясняющее идею модулей.

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

person Gert Wollny    schedule 10.05.2017

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

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

Идея, похоже, состоит в том, чтобы ввести в игру что-то более похожее на механизм C#/Java. Механизм C++ очень похож на Modula/ADA по духу. Было бы неплохо, если бы машины делали больше работы за нас.

person Kyle Huff    schedule 13.03.2018