共有ライブラリの動的リンクによりインタフェースの実装を読み込むポリモーフィズム

ヘッダファイルで定義されているdlopen,dlsym,dlerror,dlclose等の関数を用いると共有ライブラリの動的リンクを行うことができます.オブジェクトへのポインタを返す関数へのポインタをdlsymにより取得することで,特定のインタフェースを実装した未知のクラスのオブジェクトを実行時に生成し利用することができるようです.

確認のために,C++ dlopen mini HOWTOを参考にして,ありがちなAnimalインタフェースとCatクラス及びDogクラスという設定で,Animalインタフェースの異なる実装を動的に読み込みポリモーフィズムを実現するプログラムを書いてみました.

muupan/polymorphism_dynamic_link · GitHub

main.cpp

#include <iostream>
#include <cstdlib>
#include <dlfcn.h>
#include "animal.hpp"

using std::cerr;
using std::endl;
using std::string;

void *LoadFuncOrDie(void *lib, const string& func_name) {
  const auto func = dlsym(lib, func_name.c_str());
  const auto dlsym_error = dlerror();
  if (dlsym_error) {
    cerr << "Cannot load symbol create: " << dlsym_error << endl;
    dlclose(lib);
    exit(EXIT_FAILURE);
  }
  return func;
}

void *LoadLibOrDie(const string& path) {
  const auto lib = dlopen(path.c_str(), RTLD_LAZY);
  if (!lib) {
    cerr << "Cannot load library: " << dlerror() << endl;
    exit(EXIT_FAILURE);
  }
  return lib;
}

int main(int argc, char **argv) {
  const auto catlib = LoadLibOrDie("./cat.so");
  const auto doglib = LoadLibOrDie("./dog.so");
  const auto CreateCat = static_cast<AnimalCreateFunc*>(LoadFuncOrDie(catlib, "Create"));
  const auto CreateDog = static_cast<AnimalCreateFunc*>(LoadFuncOrDie(doglib, "Create"));
  {
    const auto& cat = CreateCat();
    cat->Cry();
  }
  {
    const auto& dog = CreateDog();
    dog->Cry();
  }
  dlclose(catlib);
  dlclose(doglib);
}

animal.hpp

#ifndef ANIMAL_HPP
#define ANIMAL_HPP

#include <memory>

using std::unique_ptr;

class Animal {
public:
  virtual ~Animal() {};
  virtual void Cry() const = 0;
};

typedef unique_ptr<Animal> AnimalCreateFunc();

#endif // ANIMAL_HPP

cat.cpp

#include <iostream>
#include <memory>
#include "animal.hpp"

using std::cout;
using std::endl;
using std::unique_ptr;

class Cat : public Animal {
public:
  Cat() {
    cout << "Cat was born." << endl;
  }
  ~Cat() {
    cout << "Cat died." << endl;
  }
  void Cry() const override {
     cout << "\"Meow\"" << endl;
  }
};

extern "C" unique_ptr<Animal> Create() {
  return unique_ptr<Animal>(new Cat);
}

dog.cpp

#include <iostream>
#include <memory>
#include "animal.hpp"

using std::cout;
using std::endl;
using std::unique_ptr;

class Dog : public Animal {
public:
  Dog() {
    cout << "Dog was born." << endl;
  }
  ~Dog() {
    cout << "Dog died." << endl;
  }
  void Cry() const override {
     cout << "\"Bow-wow\"" << endl;
  }
};

extern "C" unique_ptr<Animal> Create() {
  return unique_ptr<Animal>(new Dog);
}

動作確認にはGCC4.7.0を使用しました.main.cppの実行前にcat.cppとdog.cppをコンパイルしてcat.soとdog.soを作っておきます.

出力結果は下のようになりました.オブジェクト生成関数の返り値はどちらもstd::unique_ptrとして受け取っていますが,メソッドを呼ぶとポリモーフィズムが達成されているのがわかります.デコンストラクタもstd::unique_ptrによりそれぞれ正しく実行されています.

Cat was born.
"Meow"
Cat died.
Dog was born.
"Bow-wow"
Dog died.

動的リンクする共有ライブラリを切り替えればプログラムの動作を動的に変更できますし,実行時にコード生成→コンパイル→動的リンクを順に行うことでevalのようなこともできそうです.

2013/06/02追記:static_cast,overrideを使うように更新