browser icon
You are using an insecure version of your web browser. Please update your browser!
Using an outdated browser makes your computer unsafe. For a safer, faster, more enjoyable user experience, please update your browser today or try a newer browser.

封装

Posted by on 2011 年 07 月 05 日

你可以任意转载本文,但请在转载后的文章中注明作者和原始链接。
媒体约稿请联系 titilima_AT_163.com(把“_AT_”换成“@”)。

其实我一直觉得封装(encapsulation)是一个有些装逼成分在里面的词汇,因为我总觉得这东西带了些软件工程的味道,而又因为我很讨厌软件工程——大抵是由于我在大学时代接触的软件工程课程太过豆腐渣——总之,我不喜欢“封装”这个词汇,但又十分乐意做封装这件事,因为代码的重用实在太有诱惑力了。

最初自学 C++ 的时候,我对封装的认识就是把数据及其操作用 class 关键字所提供的语法支持包在一起,也就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A
{
public:
    A(int a) : m_a(a) { /* Nothing */ }
public:
    int Get(void) { return m_a; }
    int Set(int a)
    {
        int olda = m_a;
        m_a = a;
        return olda;
    }
private:
    int m_a;
};

当然,这也是 C++ class 最为广泛的存在形式。我对 PDL 的封装就使用了这种方式。

虽然这种方式被广泛使用着,但是那个名为 m_a 的成员变量却甚是扎眼。虽然 private 可以在语法上限制它的存取,但仍然改变不了它可见的这个事实——哪怕是肉眼意义上的可见。换句话说,这种暴露在外的变量声明在事实上破坏了封装的严密性。于是,我曾一度尝试了 COM 所使用的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// A.h
class IA
{
public:
    static IA* Create(int a);
    virtual void Destroy(void) = 0;
    virtual int Get(void) = 0;
    virtual int Set(int a) = 0;
};
 
// A.cpp
 
class A : public IA
{
public:
    A(int a) : m_a(a) { /* Nothing */ }
public:
    void Destroy(void)
    {
        delete this;
    }
    int Get(void) { return m_a; }
    int Set(int a)
    {
        int olda = m_a;
        m_a = a;
        return olda;
    }
private:
    int m_a;
};
 
IA* IA::Create(int a)
{
    return new A(a);
}

看起来不错,至少在 A.h 之中看不到任何碍眼的成员变量了。不过,这种方式我仍然不满意,因为这个 IA 不能封装成 DLL 给其它语言用。C 语言虽然可以使用,不过会绕很大的圈子

于是,我又借鉴了 Win32 API 的句柄思想,有了 C Style 的新封装方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// A.h
#define DEFINE_HANDLE(name)    typedef struct _ ## name { int unk; } *name
 
DEFINE_HANDLE(HANDLEA);
 
HANDLEA CreateA(int a);
void DestroyA(HANDLEA a);
int GetA(HANDLEA a);
int SetA(HANDLEA a, int value);
 
// A.cpp
 
class A
{
public:
    // ...
};
 
HANDLEA CreateA(int a)
{
    return (HANDLEA)new A(a);
}
 
void DestroyA(HANDLEA a)
{
    delete (A*)a;
}
 
int GetA(HANDLEA a)
{
    return ((A*)a)->Get();
}
 
int SetA(HANDLEA a, int value)
{
    return ((A*)a)->Set(value);
}

我在 July 4.05 的进程快照句柄实现中使用了这种方式。但事后看来,这个 C Style 的强制类型转换过于丑陋和粗暴了,不是吗?于是,这也不是我想要的,我最终还是抛弃了这种方式。
事实上,这个方法还是有改进的余地的,由于 DEFINE_HANDLE 宏随意指定了一个唯一的类型,因此使得最后的强制类型转换成为必须的工作。换句话说,如果能够做到类型统一,那么就可以避免强制类型转换了。于是,我更换了 DEFINE_HANDLE 宏的定义:

1
2
3
4
5
#ifdef __cplusplus
#define DEFINE_HANDLE(name) typedef class name ## Impl *name
#else  /* !__cplusplus */
#define DEFINE_HANDLE(name) typedef struct name ## Impl *name
#endif /* __cplusplus  */

并重新修改了 .h 和 .cpp 的实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
DEFINE_HANDLE(A);
 
A CreateA(int a);
void DestroyA(A a);
int GetA(A a);
int SetA(A a, int value);
 
// A.cpp
 
class AImpl
{
public:
    AImpl(int a) : m_a(a) { /* Nothing */ }
public:
    int Get(void) { return m_a; }
    int Set(int a)
    {
        int olda = m_a;
        m_a = a;
        return olda;
    }
private:
    int m_a;
};
 
A CreateA(int a)
{
    return new AImpl(a);
}
 
void DestroyA(A a)
{
    delete a;
}
 
int GetA(A a)
{
    return a->Get();
}
 
int SetA(A a, int value)
{
    return a->Set(value);
}

到此为止,这份代码已经可以正常工作了。不过,在实际测试的效果来看,由于类型 A 是由 DEFINE_HANDLE 宏来隐式定义的,因此 Visual Studio 与 Visual Assist 并不能识别这个类型,在 a 之后输入 -> 也无法弹出成员列表窗口。于是,我们需要再在 .cpp 之中加一遍定义:

1
typedef AImpl *A;

现在这个代码已经可以在 Visual Studio 下正常工作了,不过严格的 GCC 会认为这个 typedef 是个重复的定义。所以,这个 typedef 需要改为:

1
2
typedef AImpl *APtr;
#define A     APtr

好吧,我承认我很矫情就是了。

订阅本站

4 Comments

  • At 2011.07.05 11:08, mood said:

    克塞发情期—骚^_^

    • At 2011.07.06 22:22, eGirlAsm said:

      um..确实 每次xx-> 的时候 我们还是可以看到

      private声明部分的成员变量,很纠结,所以我是搭配

      class + c style的方式 导出接口 然后用lib的方式来控制的

      • At 2011.07.07 10:48, 匿名 said:

        兜了一圈还不是回到C, C++中采用QT中的P指针也是类似哈, 不过更像C++接口。

        • At 2011.07.07 11:02, ASMlove said:

          虽然我是个C控, 但喜欢更纯粹的C, 不喜欢C/C++混用。其实C++中采用QT的Q指针不也一样么?

          (Required)
          (Required, will not be published)