Вопрос или проблема
Я сталкиваюсь с трудностями при использовании идиомы PIMPL с модулями C++20. Я использую стороннюю библиотеку, которую не хочу раскрывать пользователю библиотеки. Я создал следующий гист, чтобы показать необходимые файлы: https://gist.github.com/mickes27/524e57c7fa95ac221be0b5ea8a9f4003. Я использую CMake, файл CMakeLists.txt включён. Я обнаружил несколько проблем:
-
Я не могу назвать InternalPartition.ixx, так как получаю ошибку C7622: расширение файла интерфейса модуля несовместимо с ‘/module:internalPartition’, так как раздел не является интерфейсом. Предполагаю, что это внутренние средства MSVC, и только интерфейсные модули (те, у которых есть ключевое слово export) могут иметь расширение .ixx.
-
Когда я импортирую InternalPartition в MyLibrary.cpp (единица реализации модуля), я получаю ошибку MyLibrary.cpp(12): ошибка C2027: использование неопределённого типа ‘lib::Internal’. Я предполагал (возможно, неправильно), что класс, объявленный в основной единице интерфейса модуля, и класс, определённый во внутреннем разделе, являются одним и тем же и должны быть видимыми (после импорта).
-
Когда я пытаюсь избавиться от предварительного объявления и импортировать InternalPartition прямо в MyLibrary.ixx, я получаю: ошибка CMake: Публичный C++ модульный источник MyLibrary.ixx требует C++ модуль MyLibrary:InternalPartition, который предоставляется частным источником – но я не хочу делать внутренний раздел частью публичного модуля, в чем тогда смысл того, чтобы он был внутренним?
-
Если я попытаюсь совсем избавиться от вещей внутреннего раздела и рассматривать InternalPartition.cpp как файл реализации модуля (module MyLibrary; вместо module MyLibrary:InternalPartition;), мы вернемся к пункту 2 – Internal нигде не указан (не имеет значения, предварительно объявляю ли я его в MyLibrary.ixx или нет).
У кого-нибудь есть идея, как я могу использовать идиому PIMPL + модули C++20, не объявляя класс Internal напрямую в MyLibrary.cpp?
Ответ или решение
Использование идиомы PIMPL с модулями C++20
Вопрос, который вы подняли, касается одновременно идиомы PIMPL (Pointer to Implementation) и новых возможностей, предоставляемых модулями C++20. Это сочетание может создавать определенные проблемы, особенно когда вы хотите скрыть детали реализации от конечных пользователей библиотеки, используя внешнюю библиотеку.
Проблемы и решения:
-
Неверное название файла
InternalPartition.ixx
:
Данное сообщение об ошибке возникает потому, что модуль, объявленный в этом файле, не является интерфейсом. В C++20 расширение.ixx
предназначено для интерфейсных модулей, которые используют ключевое словоexport
. Для внутренних модулей используйте другое расширение, например,.cpp
для файлов реализации.Решение: Переименуйте файл
InternalPartition.ixx
вInternalPartition.cpp
, если это действительно модуль реализации. -
Ошибка "использование неопределенного типа ‘lib::Internal’":
Эта ошибка указывает на то, что классInternal
, определенный в интерфейсе модуля, недоступен в реализации. В C++20, если вы импортируете модуль, вам нужно быть уверенным, что типы, которые вы хотите использовать, действительно доступны в области видимости.Решение: Убедитесь, что вы правильно импортируете
InternalPartition
вMyLibrary.cpp
и используете соответствующую директивуimport
. Вместо этого можно использовать:import MyLibrary:InternalPartition; // убедитесь, что вы импортируете правильно
Также не забудьте, что класс после определения в интерфейсном модуле должен быть доступен в реализации.
-
Ошибка CMake о "частном источнике":
Данная ошибка говорит о том, что вы пытаетесь импортировать модуль, который был определен как частный. Это происходит, если вы пытаетесь импортироватьInternalPartition
в интерфейсный модуль или в публичный контекст.Решение: Убедитесь, что
InternalPartition
не является доступным в публичной декларации. Сам концепт PIMPL означает, что реализация скрыта, и это должно справляться с ошибками компиляции. ЕслиInternalPartition
действительно должен быть приватным, оставьте его только в реализации, а сам интерфейс должен предоставлять только то, что нужно пользователям. -
Ошибки с использованием
InternalPartition.cpp
:
Если вы хотите избавиться от внутреннего модуля и писатьmodule MyLibrary;
вInternalPartition.cpp
, необходимо убедиться, что все необходимые декларации о классеInternal
доступны вMyLibrary.ixx
. Если классInternal
не объявлен вMyLibrary.ixx
, компилятор не сможет его идентифицировать.Решение: Возможно, имеет смысл оставить
InternalPartition
внутренним, но не управлять его объявлением в интерфейсе. Другими словами, используйтеForward Declaration
вMyLibrary.ixx
и убедитесь, что его реализация полностью закрыта.
Пример реализации
Для лучшего понимания, вот простой пример реализации с использованием PIMPL и C++20 модулей:
MyLibrary.ixx:
export module MyLibrary;
class Internal; // Forward declaration
export class MyLibrary {
public:
MyLibrary();
~MyLibrary();
private:
Internal* impl; // PIMPL
};
InternalPartition.cpp:
module MyLibrary:InternalPartition;
class Internal {
public:
void doSomething();
};
// Реализация функций классов
MyLibrary.cpp:
import MyLibrary:InternalPartition;
MyLibrary::MyLibrary()
: impl(new Internal()) {}
MyLibrary::~MyLibrary() {
delete impl;
}
Заключение
Использование PIMPL в сочетании с модулями C++20 требует точного понимания видимости и доступности модулей. Следуя приведенным рекомендациям, вы сможете создать библиотеку, основанную на PIMPL, которая устойчиво скрывает реализацию от пользователей, при этом сохраняя все преимущества модульной структуры C++. Не забывайте проверять компиляцию вашего проекта с использованием итогов CMake, чтобы упростить процесс отладки.