2013年8月21日 星期三

C++: 使用 private virtual 區分實作介面的 method

問題描述

Java 有語法 interface 明確定義 class 之間的接口, 但是 C++ 沒有, 只能透過「習以為常」的慣例表示, 也就是:

  • class I 宣告一組 public pure virtual function, 表示 I 是一個 "interface"
  • class A 希望實作 I, 於是透過繼承的方式實作 I
  • 需要用 I* 的 class, 取得實作 I 的物件 (也就是 A 的物件), 存成 I*

當 class A 需要實作多組介面, 或是自己也有一些 public method 供別人使用時, 看 class A 的宣告會不易找出那些是 A 的 public API, 比方說以下的例子:

class I
{
  public:
    virtual void foo() = 0;
    virtual void bar() = 0;
};

class A : public I
{
  public:
    void handle();
    virtual void process();

    // I's methods.
    virtual void foo();
    virtual void bar();
};

一眼看去有四個 method, 實際上可能只有 handle() 和 process() 才是使用 A 的人需要關心的。當 A 同時實作 (繼承) 多組介面就不易閱讀了。

解法

若宣告實作介面的 method 為 private, 像下面這樣:

class A : public I
{
  public:
    void handle();
    virtual void process();

  private:
    // I's methods.
    virtual void foo();
    virtual void bar();
};

有幾點好處:

  • 降低這些 method 可存取的範圍, 避免被誤用
  • 縮小閱讀程式的範圍
  • 間接暗示它們是作為 I 的介面使用, 若 A 自己的程式碼都沒用到它們的話, 會更明確地表明此點。特別適合用於 callback 的介面。

宣告 private virtual 的基本精神和宣告 member field 為 private 差不多: 不需要用到的, 就不要曝露出去, 易於日後維護。

相關閱讀

本篇討論的使用情境很窄, 專注於一點: 需要在 C++ 的世界裡表示出「Java interface」的語意時, 可使用「在介面 class 宣告 public pure virtual method + 在實作 class 宣告 private virtual method」。

Virtuality 提出關於 virtual 的四項要點:

  • Prefer to make interfaces nonvirtual, using Template Method.
  • Prefer to make virtual functions private.
  • Only if derived classes need to invoke the base implementation of a virtual function, make the virtual function protected.
  • A base class destructor should be either public and virtual, or protected and nonvirtual.

涵蓋關於 private virtual 的更多應用情境, 滿不錯的參考資料。

2 則留言:

  1. 我覺得最主要的是interface跟customize method分開來, interface不需要用virtual, 而是把需要customize的部分放在private virtual.

    一方面可以針對customize的部分做一些pre-process or post-process, 因為是放在interface method 而不是在virtual method. 也可以規範customized的行為 (像是一定要夾在某個method之後)

    可讀性我覺得差不多

    回覆刪除
    回覆
    1. > 我覺得最主要的是interface跟customize method分開來, interface不需要用 virtual, 而是把需要customize的部分放在private virtual.
      >
      > 一方面可以針對customize的部分做一些pre-process or post-process, 因為是放在interface method 而不是在virtual method. 也可以規範customized的行為 (像是一定要夾在某個method之後)

      適合用 Template method 的情境, 這樣做確實很棒, 像是 XML parser 的 callback, 或是某些操作的 pre-commit, post-commit

      > 可讀性我覺得差不多

      我原本的出發點是為啥要用 private virtual, 查一查後想通背後的意思, 再回頭從需求出發, 描述這樣作的好處

      method 一大票時這樣滿方便的, 基本精神和 member field 預設都 private 差不多, 不該用到的, 就不要曝露出來

      來稍微改寫一下文章, 補充這點

      刪除

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...