Pimpl-idiom на языке программирования D

D имеет фантастическую модульную систему, которая значительно сокращает время компиляции по сравнению с С++. Согласно документации D все еще содержит непрозрачные структуры и союзы, чтобы позволить идиому pimpl. Мой вопрос: как я могу объявить вложенную структуру (или объединение) в одном модуле и определить ее в другой? Каков синтаксис для этого?

В С++ заголовок будет выглядеть следующим образом:

struct S { 
    ... 
    struct Impl; 
    Impl * p; 
};

и файл реализации (cpp файл) будет использовать интересный :: -syntax, как это:

#include "header.h"
struct S::Impl { 
    ... 
};

Как реализовать то же самое в D?

Ответ 1

D (как минимум, DMD) использует .di файлы для деклараций. Они несколько эквивалентны файлам C .h, однако они являются необязательными. Компилятор D может автоматически генерировать файлы .di (когда указан параметр -H), хотя я считаю, что в настоящее время все, что он делает, это тело функции полосы и unittests.

Здесь один из способов достижения PImpl с использованием файлов .di:

  • mod.di:

    struct S
    {
        struct I;
        I* pi;
    }
    
  • mod.d:

    struct S
    {
        struct I
        {
            int v;
        }
    
        I* pi;
    }
    

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

Ответ 2

Мой вопрос: как я могу объявить вложенную структуру (или объединение) в одном модуле и определить ее в другом?

Чтобы получить это прямо - это намеренно невозможно в D по дизайну. Это прямое следствие наличия надежной модульной системы - каждое объявление символа неявно квалифицируется по имени модуля, которое оно объявлено внутри. И по целому ряду причин вы не можете захватить символ в другие "пространство имен" модулей.

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

Ответ 3

Другой подход основан на системе D иерархии классов:

все объекты наследуют явно или неявно Object.

Итак, идея состоит в том, чтобы реализовать OuterClass с помощью pimpl, сгенерировать соответствующие di-file, вручную удалите все определения OuterClassPrivate из di файла и изменение объявления элемента pimpl.

Например:

первая версия разделяемой библиотеки

module pimpl.mylib;

class PimplTest
{
    this()
    {
        mImpl = new PimplTestPrivate();
    }

    ~this()
    {
    }

    string sayWhat(string what)
    {
        return mImpl.ku ~ " " ~ what;
    }

    private class PimplTestPrivate
    {
        string ku = "Ku!!1";
    }

    private PimplTestPrivate mImpl;
}

тестовое приложение:

module main;

import std.stdio;
import pimpl.mylib;

void main()
{
    PimplTest t = new PimplTest();
    writeln(t.sayWhat("?"));
}

Общий mylib может быть построен следующим образом (под Linux):

$ dmd -H -c mylib.d -fPIC
$ dmd -ofmylib.so mylib.o -shared -defaultlib=libphobos2.so -L-rpath=/path/to/where/shared/phobos/library/is

Затем отредактируйте файл в формате genereated di-file:

// D import file generated from 'mylib.d'
module pimpl.mylib;
class PimplTest
{
    this();
    ~this();
    string sayWhat(string what);

    // NOTE this
    private Object mImpl;
}

Скомпилируйте тест

$ dmd -c main.d /path/to/first/version/of/mylib.di
$ ln -s /path/to/first/version/of/mylib.so .
$ dmd main.o -L-l:mylib.so -defaultlib=libphobos2.so -L-rpath=/path/to/where/shared/phobos/library/is:.
$ ./main
Say: ?

Затем мы меняем mylib:

module pimpl.mylib;

import std.conv;

class PimplTest
{
    this()
    {
        mImpl = new PimplTestPrivate();
    }

    ~this()
    {
    }

    string sayWhat(string what)
    {
        return mImpl.getMessage1(mImpl.getValue(), what);
    }

    private class PimplTestPrivate
    {
        int getValue()
        {
            return 42;
        }

        string ku = "Ku!!1";

        string getMessage1(int x, string y)
        {
            return "x = " ~ to!(string)(x) ~ ", " ~ y;
        }

        double pi = 22.0/7.0;
    }

    private PimplTestPrivate mImpl;
}

Скомпилируйте его и замените двоичный общий объект (так файл) первой версии mylib только что построенным. Запуск тестового приложения не должен вылетать, но результат будет другим.