2014年5月18日 星期日

C++ inline namespace

最近在研究 libc++ 的原始碼,意外的發現 inline namespace 這個有趣的功能。一言以蔽之,inline namespace 讓我們可以在 C++ 程式碼層級做 symbol versioning。

設想一個情境:你今天要設計一個函式庫。你可以預期這個函式庫的生命週期會很長,而且你未必能讓所有的使用者重新編譯他們的程式。這意謂著:你對 Object Layout 的任何改動都有可能破壞向下相容。這時候我們就會想要提供不同版本的函式。但是實務上該怎麼做呢?

舉例來說,我們要實作 std::string 類別。最早我們寫的版本是一個簡單的實作:

namespace std {
  class string {
  private:
    size_t size;
    size_t capacity;
    char *data;
  public:
    string(const char *s);
    const char *c_str() const {
      return data;
    }
  };

  string::string(const char *s)
      : size(std::strlen(s)), capacity(0), data(nullptr) {
    data = new char[size + 1];
    capacity = size + 1;
    memcpy(data, s, size + 1);
  }
}


可是隨著時間的演進,我們想採用 Small String Optimization (SSO) 以取得更高的效能與空間利用率。這時候我們必需把 std::string 改寫成:

namespace std {
  class string {
  private:
    size_t size;

    union {
      struct {
        size_t capacity;
        char *data;
      } normal;

      struct {
        char data[sizeof(size_t) + sizeof(char *)];
      } sso;
    };

  public:
    string(const char *s);

    const char *c_str() const {
      if (size < sizeof(sso.data)) {
        return sso.data;
      } else {
        return normal.data;
      }
    }
  };

  string::string(const char *s): size(std::strlen(s)) {
    if (size < sizeof(sso.data)) {
      memcpy(sso.data, s, size + 1);
    } else {
      normal.data = new char[size + 1];
      normal.capacity = size + 1;
      memcpy(normal.data, s, size + 1);
    }
  }
}


然而這個改動有可能會對舊的 Binary 造成困擾。如果 c_str() 有被 inline 但 string(const char *) 建構子沒有被 inline,則執行期會產生「字串內容」被當成 data_ 指標的嚴重問題。

我們該怎麼避免這個問題呢?我們可以用 Namespace 適當分割不同的實作:

namespace std {
  namespace v1 {
    class string { ... };
  }
  namespace v2 {
    class string { ... };
  }
}


可是這樣我們必須用 std::v1::string 或 std::v2::string 來指稱我們的 string 類別。這當然不滿足我們的期望。畢境所有的使用者還是使用 std::string 宣告一個字串。

這時就輪到 inline namespace 出場了!嚴格來說,inline namespace 是編譯器提供的 syntax sugar。他讓我們可以省略那個 namespace 的 scope 運算子。舉例來說,如果我要以 std::v2::string 的實作作為預設的版本,我可以把上面的例子改成:

namespace std {
  namespace v1 {
    class string { ... };
  }
  inline namespace v2 {
    class string { ... };
  }
}


這樣一來,我寫 std::string 其實就會是 std::v2::string。

當然,你也可以善用 Preprocessor 讓不同的程式可以依據特定條件選擇特定實作。例如:libfoo 是舊的函式庫,所以如果 libfoo 引入這個標頭檔,我們就把 v1 宣告為 inline namespace;libbar 是新的函式庫,所以如果 libbar 引入這個標頭檔,我們就把 v2 宣告為 inline namespace。

namespace std {
#if defined(LIBFOO)
  inline
#endif
  namespace v1 {
    class string { ... };
  }


#if !defined(LIBFOO)
  inline
#endif
  namespace v2 {
    class string { ... };
  }
}


附帶一提,inline namespace 只是 syntax sugar 對我們有一個實際上的好處:從 Linker 或 Loader 的角度來看,這些都是不同的函式,所以 std::v1::string 和 std::v2::string 的實作可以並存。不同來源的 Binary 可以各自取用他們所需的實作。

2014年5月3日 星期六

使用 git subtree 切分版本庫

有候我們希望把一個大的 Git Repository 切分成多個 Repositories。這時我們就可以用 git subtree 這個指令。以下我以 Android NDK 為例子:

$ git clone https://android.googlesource.com/platform/ndk
$ cd ndk

分割 sources/cxx-stl/llvm-libc++abi/libcxxabi 下面的 commits:

$ git subtree split -P sources/cxx-stl/llvm-libc++abi/libcxxabi -b libcxxabi

之後只要另外在不同的資料夾執行:

$ cd ..
$ git init libcxxabi
$ cd libcxxabi
$ git pull ../ndk libcxxabi:master

就可以有一個完全獨立的版本庫了!

參考資料

2014年4月21日 星期一

Ubuntu 14.04 與 ASUS UX32VD 的小問題

因為 Ubuntu 12.04 不能正常驅動 ASUS UX32VD 的顯示卡,所以一直以來我都是用 Ubuntu 13.04 的 Kernel 搭配 12.04 的應用程式。這次釋出的 Ubuntu LTS 14.04 初步測試沒有問題就升級了。不過目前有遇一些小問題,暫時用以下方法解決:

內建的喇叭沒有辦法發出聲音


不知道為什麼我不在 audio 群組之內,所以我只能從外接孔聽到聲音。把我自己加到 audio 群組即可。

$ sudo adduser $USER audio

IBUS 變得不太討喜


新版的 IBUS 除了不能用 Ctrl+Space 切換中英輸入之外(需改用 Shift),會跟著插入點亂跑的輸入法狀態視窗真的很礙眼。因此目前暫時改用 GCIN:

$ sudo apt-get install gcin

之後再去「系統設定值 / 語言支援 / 鍵盤輸入法系統」選擇 GCIN。

不過 GCIN 似乎也有一些問題,無法從狀態列看到 GCIN 的圖示。或許是 Unity 或 AppIndicator 的問題,但是初步測試,用 dconf-editor 修改「com / canonical / unity-gtk-module / whitelist」也沒有用。

SVN 格式更新


以前的 Subversion 版本庫要用以下指令轉換檔案格式:

$ svn upgrade

預設似乎沒有辦法用 apt-get 安裝 i386 的軟體


$ sudo dpkg --add-architecture i386
$ sudo dpkg --print-foreign-architectures
i386

Ubuntu 14.04 無法正常讀取部分使用 PTP 協議的相機


部分過去在 12.04 可以正常使用的數位相機,在 14.04 就沒有辦法正常的被偵測到。舉例來說,我使用的 Nikon Coolpix S6200 就中標了。從錯誤回報來看,應該是 gvfs 有點問題。

除了直接改用讀卡機存取照片外,另一個可行的方法是使用 gphoto2 這個 command line 工具。

$ sudo apt-get install gphoto2
$ mkdir ~/Camera
$ cd ~/Camera
$ gphoto2 -P

小結


目前就只遇到這些問題,其他以後想到再補好了。