Модули C++20 и идиома PIMPL

Вопрос или проблема

Я сталкиваюсь с трудностями при использовании идиомы PIMPL с модулями C++20. Я использую стороннюю библиотеку, которую не хочу раскрывать пользователю библиотеки. Я создал следующий гист, чтобы показать необходимые файлы: https://gist.github.com/mickes27/524e57c7fa95ac221be0b5ea8a9f4003. Я использую CMake, файл CMakeLists.txt включён. Я обнаружил несколько проблем:

  1. Я не могу назвать InternalPartition.ixx, так как получаю ошибку C7622: расширение файла интерфейса модуля несовместимо с ‘/module:internalPartition’, так как раздел не является интерфейсом. Предполагаю, что это внутренние средства MSVC, и только интерфейсные модули (те, у которых есть ключевое слово export) могут иметь расширение .ixx.

  2. Когда я импортирую InternalPartition в MyLibrary.cpp (единица реализации модуля), я получаю ошибку MyLibrary.cpp(12): ошибка C2027: использование неопределённого типа ‘lib::Internal’. Я предполагал (возможно, неправильно), что класс, объявленный в основной единице интерфейса модуля, и класс, определённый во внутреннем разделе, являются одним и тем же и должны быть видимыми (после импорта).

  3. Когда я пытаюсь избавиться от предварительного объявления и импортировать InternalPartition прямо в MyLibrary.ixx, я получаю: ошибка CMake: Публичный C++ модульный источник MyLibrary.ixx требует C++ модуль MyLibrary:InternalPartition, который предоставляется частным источником – но я не хочу делать внутренний раздел частью публичного модуля, в чем тогда смысл того, чтобы он был внутренним?

  4. Если я попытаюсь совсем избавиться от вещей внутреннего раздела и рассматривать 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. Это сочетание может создавать определенные проблемы, особенно когда вы хотите скрыть детали реализации от конечных пользователей библиотеки, используя внешнюю библиотеку.

Проблемы и решения:

  1. Неверное название файла InternalPartition.ixx:
    Данное сообщение об ошибке возникает потому, что модуль, объявленный в этом файле, не является интерфейсом. В C++20 расширение .ixx предназначено для интерфейсных модулей, которые используют ключевое слово export. Для внутренних модулей используйте другое расширение, например, .cpp для файлов реализации.

    Решение: Переименуйте файл InternalPartition.ixx в InternalPartition.cpp, если это действительно модуль реализации.

  2. Ошибка "использование неопределенного типа ‘lib::Internal’":
    Эта ошибка указывает на то, что класс Internal, определенный в интерфейсе модуля, недоступен в реализации. В C++20, если вы импортируете модуль, вам нужно быть уверенным, что типы, которые вы хотите использовать, действительно доступны в области видимости.

    Решение: Убедитесь, что вы правильно импортируете InternalPartition в MyLibrary.cpp и используете соответствующую директиву import. Вместо этого можно использовать:

    import MyLibrary:InternalPartition; // убедитесь, что вы импортируете правильно

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

  3. Ошибка CMake о "частном источнике":
    Данная ошибка говорит о том, что вы пытаетесь импортировать модуль, который был определен как частный. Это происходит, если вы пытаетесь импортировать InternalPartition в интерфейсный модуль или в публичный контекст.

    Решение: Убедитесь, что InternalPartition не является доступным в публичной декларации. Сам концепт PIMPL означает, что реализация скрыта, и это должно справляться с ошибками компиляции. Если InternalPartition действительно должен быть приватным, оставьте его только в реализации, а сам интерфейс должен предоставлять только то, что нужно пользователям.

  4. Ошибки с использованием 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, чтобы упростить процесс отладки.

Оцените материал
Добавить комментарий

Капча загружается...