Форвардное объявление, чтобы сломать зависимость cycli c в модулях C ++ 20 не работает - PullRequest
6 голосов
/ 27 марта 2020

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

Я использую компилятор MSV C, поставляемый с Visual Studio Preview 16.6.0 2.0 . Я знаю, что это еще не стабильный выпуск, но я бы хотел поиграться с новыми функциями, чтобы начать их изучение.

В основном я написал модуль (myModule) и 2 раздела этого модуля (mySubmodule1 и mySubmodule2), и я реализовал их в двух файлах реализации модуля (mySubmodule1Impl.cpp и mySubmodule2Impl.cpp).

mySubmodule1 зависит от mySubmodule2 и наоборот. Вот источник:

mySubmodule1.ixx

export module myModule:mySubmodule1;

export namespace myNamespace{

class MyClass2;

class MyClass1{
    public:
    int foo(MyClass2& c);
    int x = 9;
};
}

mySubmodule2.ixx

export module myModule:mySubmodule2;
import :mySubmodule1;

export namespace myNamespace{

class MyClass2 {
    public:
    MyClass2(MyClass1 x);
    int x = 14;
    MyClass1 c;
};
}

mySubmodule1Impl. cpp

module myModule:mySubmodule1;
import :mySubmodule2;

int myNamespace::MyClass1::foo(myNamespace::MyClass2& c) {
    this->x = c.x-14;
    return x;
}

mySubmodule2Impl. cpp

module myModule:mySubmodule2;
import :mySubmodule1;

myNamespace::MyClass2::MyClass2(myNamespace::MyClass1 c) {
    this->x = c.x + 419;
}

myModule.ixx

export module myModule;

export import :mySubmodule1;
export import :mySubmodule2;

Как видите, я могу переслать объявление MyClass2 в mySubmodule1, но я не могу переслать объявление MyClass1 в mySubmodule2, потому что в MyClass2 я использую конкретный объект типа MyClass1.

Я компилирую с этой строкой: cl /EHsc /experimental:module /std:c++latest mySubmodule1.ixx mySubmodule2.ixx myModule.ixx mySubmodule1Impl.cpp mySubmodule2Impl.cpp Source.cpp, где Source.cpp является просто основным.

Я получаю печально известную ошибку C2027: использование неопределенного типа 'myNamespace :: MyClass2' в mySubmodule1Impl.cpp и mySubmodule2Impl.cpp в строках, где я использую MyClass2. Более того, компилятор говорит мне взглянуть на объявление MyClass2 в mySubmodule1.ixx, где есть предварительное объявление.

Теперь я действительно не понимаю, где моя ошибка. Я проверял снова и снова, но логика c программы кажется мне идеальной. Порядок компиляции файлов должен определять MyClass2 до того, как он будет использован в реализации!

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

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

Пожалуйста, не говорите, что циклические c зависимости плохие. Я часто знаю, что они плохие, но не всегда. И даже если вы думаете, что они всегда плохие, я не прошу большого пальца. Я спрашиваю, почему мой код компилируется со «старым» .h +. cpp, но не с новыми модулями. Почему компоновщик не видит определение MyClass2.


EDIT 1

Вот новый дизайн, предложенный в ответе, но он все еще не работает. Я получаю точно такие же ошибки:

mySubmodule1Impl. cpp

module myModule;

int myNamespace::MyClass1::foo(myNamespace::MyClass2& c) {
    this->x = c.x-14;
    return x;
}

mySubmodule2Impl. cpp

module myModule;

myNamespace::MyClass2::MyClass2(myNamespace::MyClass1 c) {
    this->x = c.x + 419;
}

Все остальные файлы не изменены.

1 Ответ

6 голосов
/ 28 марта 2020

Непосредственная проблема заключается в том, что вы не можете иметь «интерфейсный файл» и «файл реализации» для одиночного раздела модуля (как если бы это был заголовочный файл и пара исходных файлов). Существуют интерфейсные разделы и разделы реализации, но у каждого должно быть свое имя, потому что каждый существует для import . Конечно, одна из целей модулей - разрешить один файл, в котором были нужны пары заголовок / источник: вы часто можете включить реализацию в тот же файл, что и файл интерфейса, но использовать export и / или inline только с последним. Это идет с обычным недостатком только для заголовка, вызывая более частые повторные сборки.

Метапроблема состоит в том, что здесь нет округлости : вы уже обратились к нему с предварительным объявлением MyClass2. Это правильно: модули не меняют базовую семантику C ++, поэтому такие методы остаются применимыми и необходимыми. Вы по-прежнему можете разделить классы на два файла по обычным организационным причинам, но нет необходимости, чтобы определения методов вообще были в разделах (ни в отдельных module myModule; единицах реализации) , который автоматически импортирует весь интерфейс). import :mySubmodule1, которое остается (в интерфейсном разделе mySubmodule2), тогда является однозначным и правильным.

Что касается заявленного декларации о владении s, они появились в TS модулей, которые не имеет разделов модуля, так что подобные случаи не могут быть обработаны иначе (поскольку вы можете использовать обычное прямое объявление для сущности из другого раздела , но не другого модуля ).

...