D编程语言中的Pimpl习语



D有一个出色的模块系统,与C++相比,它大大缩短了编译时间。根据文档D仍然提供了不透明的结构和并集,以便启用皮条习惯用法。我的问题是:如何在一个模块中声明嵌套结构(或并集),并在另一个模块中将其定义?它的语法是什么?

在C++中,标头看起来像这个

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

实现文件(cpp文件)将使用一些看起来很有趣的::-语法,如下所示:

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

如何在D中实现相同的功能?

D(至少是DMD)使用.di文件进行声明。它们在某种程度上等同于.h文件,但它们是可选的。D编译器可以自动生成.di文件(当指定了-H开关时),尽管我相信目前它所做的只是剥离函数体和单元测试。

以下是使用.di文件实现PImpl的一种方法:

  • mod.di:

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

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

请注意,目前您有责任确保S中的字段在.d.di文件中相同-如果它们不同,编译的模块将对字段的布局有不同的了解,这可能会导致内存损坏。当前的编译器实现不会验证.d.di文件中的定义是否匹配。

我的问题是:如何在一个模块中声明嵌套结构(或并集),并在另一个模块中将其定义?

为了弄清楚——在D中,这是故意不可能的。这是拥有可靠模块系统的直接结果——每个符号声明都由其内部声明的模块名称隐式限定。由于各种原因,您不能将一个符号劫持到另一个模块的"命名空间"中。

也就是说,不需要在同一模块中使用pimpl方法。有关更多详细信息,您可以参考CyberShadow答案。

另一种方法是基于类层次结构的D系统:

所有对象都显式或隐式继承对象

因此,我们的想法是用pimpl实现OuterClass,生成相应的di文件,手动从di文件中删除OuterClassPrivate的所有定义以及更改皮条客成员的声明。

例如:

共享库的第一个版本

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

然后编辑生成的di文件:

// 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;
}

编译它并用刚构建的二进制共享对象(so文件)替换mylib第一个版本的二进制共享文件。运行测试应用程序不能崩溃,但输出会有所不同。

最新更新