2014年5月28日水曜日

とりあえずBoost.Pythonを使う(5) return_internal_referenceで==をしてみると…

Boost.Pythonで保有しているオブジェクトをgetterなどで返すときはreturn_internal_referenceを指定すると便利だ。
class_<Surface>("Surface", init<>())
        .def("__str__", &Surface::toString)
        .add_property("shape", 
                      make_function(&surface_get_shape, return_internal_reference<>()),
                      make_function(&surface_set_shape, with_custodian_and_ward_postcall<1, 2>())
                      )

こうするとオブジェクトを取り出して値を変更すると内部の値も変わっている。
唐突だが、以下の例では途中で片方の値だけ変更したけど、もう片方も変わっている。
shape = eva.Sphere(13,2)
self.surf.shape = shape

a = self.surf.shape
b = self.surf.shape
print(a,b)

a.r = 1000
print(a,b)

出力結果:
Sphere r: 13, erad: 2 Sphere r: 13, erad: 2
Sphere r: 1000, erad: 2 Sphere r: 1000, erad: 2

これは想定内の働き。でもここで
a == b
とすると、Falseになってしまう。なぜだ。
中身をみるとどうやらアドレスが別らしい。BoostPython側で何か処理をしてくれていてこうなったのだろう。


こうなるとオブジェクトが等しいという判断は、オブジェクトのアドレスでなく、中身の値で判断させるようにしたほうが良さそうだ。

そこで必ず==を実装するようにすべく、
C++のVirutualクラスに==や!=をオーバーライドさせるように書く。
引数がネックになってどう書いていいかわからなかった。
書き方を調べると以下のような記事が見つかった。
https://groups.google.com/forum/#!topic/comp.lang.c++.moderated/VAm49RqRico


メモがてら抜粋させていただくと
bool Derived::operator==(const Base &rhs) const
{
    const Derived *pRhs = dynamic_cast<const Derived *>(&rhs);
    if (pRhs == NULL) { // rhs is of another type derived from Base
         return false;
     } else {
          // Do work to compare two Derived objects
     }
}
違うクラスが来るとfalseになるような仕様になっている。
コンパイルも通ったし、めでたしめでたし。

ただ、質問の解答は下に続いていて、
そもそもPure Virtualクラスに==をオーバライドさせるとか設計が悪すぎるとか書いてある。んーどうすれば一番良いのだろうか…





2014年5月25日日曜日

とりあえずBoost.Pythonを使う(4) pythonのリストと関数のオーバーライド

前にも書いた気がするけど、Boost PythonでC++のベクトルを返すときにvector_index_suiteを使えば受け渡しが簡単だ。だけど、Pythonのリストとして渡したり、返したりしたほうがPython側からすればシームレスに扱えてよい。そんな時はboost::python::listを使う。
例えば
void Hoge::setX(std::vector<double> &x) {
    _x = x;
}

というC++のメソッドがあった場合Pythonでは
>>> hoge.setX([1,2,3])
としたい。

そんなときはboost::python::listを引数にして、boost::python::extrac<>で値を抽出すればよい。
void Hoge::setX(boost::python::list &x) {
    std::vector<double> tmp;
    int len = boost::python::len(x);
    for(int i=0; i<len; i++) {
        tmp.push_back(boost::python::extract<double>(x[i]));
    }
    _x = tmp;
}

こうやってしまうと元の関数を書きなおしてしまうでのC++を使う場合には使いにくい。
同じ名前のメソッドを作ってその内1つをPythonのWrapperに登録する。
static void hoge_set_x(Hoge &h, boost::python::list &x){hoge.setX(x);}

boost::python::class_<Hoge>("Hoge")
               .def("setX", &hoge_set_x)
               ;

ただ、このやり方だと、Wrapperを2つも各のでちょっと周りくどい。しかもlistとvectorをいちいちコピーしないといけない。index_suiteを使った良いやり方がありそうな気がするけど、std::vector<std::vector<> >でエラーがでたので、今回は諦めて上のようにしてみた。

containerのラッパーを書いている以下のようなサイトも見つかった。
https://wiki.python.org/moin/boost.python/StlContainers
大変なので今後の参考としておこう。





2014年5月18日日曜日

とりあえずBoost.Pythonを使う(3) Abstract Class

Boost.Pythonを使ってC++の継承関係をラップすることもできる。
親クラスがvirtual=0なメソッドを持っているときは、(Abstract ClassとかInterfaceとか)は以下のようにすればよい。
class BaseShape {
public:
    virtual ~BaseShape(){}
    virtual std::string toString() const = 0;
};

class Sphere : public BaseShape {
public:
    Sphere(double r);
    ~Sphere();
    std::string toString() const;

private:
    double _r;
};

ラッパー側は以下のようにする。
    class_<BaseShape, boost::noncopyable>("BaseShape", no_init)
        .def("__str__", &BaseShape::toString)
        ;
    class_<Sphere, bases<BaseShape> > ("Sphere", init<double>())
        .def("__str__", &Sphere::toString)
        ;

no_initを記述してなくて、コンパイル時ずっとabstract classがどうとかいうエラーが出ていた。

Boost.PythonをPython3で使う。

HomebrewでインストールされるBoost.pythonは「python」コマンドで呼び出されるpythonを使ってコンパイルさえるのでデフォルトでは2になっている。

でも今後はPython3で開発したいので、Boost.PythonもPython3対応にしてみる。
HomebrewではPython3の指定の仕方がわからないので自分でビルドする。ビルドとなると一歩引いてしまう自分がいたが、簡単だった。

まずboostをダウンロードする。
そのフォルダに移動して
./bootstrap.sh --with-libraries=python --with-python=python3 --with-python-version=3.3

とすれば設定ファイルproject-config.jamが出力される。--with-librariesで欲しいものだけコンパイルできるようだ。出力されたファイルをみてPython3になっていればよい。

それで
./b2

とすればコンパイルが始まる。エラーが結構でたきがするけど、気にしないでおこう。
最後にメッセージが出る。

The following directory should be added to compiler include paths:

    /Users/yourname/Downloads/boost_1_55_0

The following directory should be added to linker library paths:

    /Users/yourname/Downloads/boost_1_55_0/stage/lib


指定されたフォルダを見て、libboost_python3.aがあればOK.
コンパイル時にそれをリンクすればPython3とC++の連携ができる。

2014年5月14日水曜日

とりあえずBoost.Pythonを使う(2) dictを使う

Boost.Pythonを使ってC++とPython間でdictionaryのやり取りを行う。
まずはじめに出会う例はmap_index_suiteを使う例だが、これはpythonで使う場合に.keys()などのメソッドが使えないので困る。
なのでboost::python::dictを直接やり取りするようなメソッドを準備する。

まずPythonのdictionaryをC++のmapにセットする場合のクラス・メソッドは例えば以下のようになる。

#include <boost/python.hpp
void Hoge::set_dict(const boost::python::dict &pydict)
{
    std::map<std::string, double> newmap;

    int len = boost::python::len(pydict);
    boost::python::list keys = pydict.keys();

    for(int i=0; i < len; i++) {
        std::string k = boost::python::extract<std::string>(keys[i]);
        newmap[k] = boost::python::extract<double>(pydict[k]);
    }
    _dict = newmap;
}

逆にC++のマップをPythonのdictionaryとして返す場合は以下のようにする。
boost::python::dict Hoge::get_dict() const
{
    boost::python::dict pydict;

    std::map<std::string, double>::const_iterator it;
    for(it = _dict.begin(); it != _dict.end(); it++)   
          pydict[it->first]=it->second;        
    
    return pydict; 
}




2014年5月13日火曜日

とりあえずBoost.Pythonを使う(1)

Boost.PythonをつかってC++のクラスをPythonへ渡す方法。
この例では、

  1. メンバ変数
  2. メソッド
  3. コンストラクタ
  4. オペレータ
  5. Pythonリストの受け渡し

が分かる仕組みになっている。
例えば3次元ベクトルクラスVector3dをC++で書いたとする。Pythonで扱うとしたら以下のようにしたい。

 v1 = vector3d(1,2,3)   #constructor
 v2 = vector3d([1,2,3]) #constructor with list
 v1.x                   #member
 v1.norm()              #method
 v1+v2                  #operator


ラッパーは以下のように作ってみた。
Pythonのlistとやり取りするクラスが始めから備わっているようだ。
extract<>についてはhttps://wiki.python.org/moin/boost.python/extract


#include <math.h>
#include <boost/python.hpp>

class Vector3d {
public:
    double x;
    double y;
    double z;

    //constructor
    inline Vector3d(double x, double y, double z):
            x(x), y(y), z(z) {}

    //constructor with python linst
    inline Vector3d(boost::python::list pl)
    {
        x = boost::python::extract<double>(pl[0]);
        y = boost::python::extract<double>(pl[1]);
        z = boost::python::extract<double>(pl[2]);

    }
    
    inline Vector3d operator+(const Vector3d &v) const
    {
        return Vector3d(x+v.x, y+v.y, z+v.z);
    }

    inline Vector3d& operator+=(const Vector3d &v)
    {
        x += v.x;
        y += v.y;
        z += v.z;
        return *this;
    }

    //return l2 norm
    inline double norm() const
    {
        return sqrt(x*x + y*y + z*z);
    }

    //to python list
    inline boost::python::list toList() const
    {
        boost::python::list pl;
        pl.append(x);
        pl.append(y);
        pl.append(z);
        return pl;
    }

};



BOOST_PYTHON_MODULE(hoge)
{
 class_<Vector3d>("vector3d", init<double, double, double>())
  .def(init<boost::python::list>())
  .def_readwrite("x", &Vector3d::x) //member variables
  .def_readwrite("y", &Vector3d::y)
  .def_readwrite("z", &Vector3d::z)
  .def(self + self)    //operator
  .def(self += self)
  .def("norm",     &Vector3d::norm) //method
  .def("tolist", &Vector3d::toList) //to python list
  ;
}






2014年5月11日日曜日

とりあえずBoost.Pythonを使う(0)  Makefile for Homebrewer

Boost PythonをOSXで使ってC++モジュールをPythonから使うことができた。

http://d.hatena.ne.jp/moriyoshi/20091214/1260779899を参考に始めた。

上のサイトのコンパイルオプションだと、コンパイルはとおるけど、エラーが出てしまう。
>>> import basic
>>> a = basic.accumulator()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() should return None, not 'NoneType'

BoostPythonをビルドしたときに使用したPythonを使用しないとコンパイルは通るけど、使用時にエラーが起きるようだ。なので以下のようなMakefileを作ったらうまくいった。

#makefile for boost python
#PYTHON_ROOT = /usr/local/Cellar/python3/3.3.5/Frameworks/Python.framework/Versions/3.3
PYTHON_ROOT = /usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7

# location of the Boost Python include files and library
 
BOOST_INC = /usr/local/Cellar/boost/1.55.0_1/include
BOOST_LIB = /usr/local/Cellar/boost/1.55.0_1/lib
 
# compile mesh classes
TARGET = basic
 
$(TARGET).so: $(TARGET).o
    g++ -bundle -Wl $(TARGET).o -lboost_python -L$(BOOST_LIB) -L$(PYTHON_ROOT)/lib -lpython2.7 -o $(TARGET).so
 
$(TARGET).o: $(TARGET).cpp
    g++ -I$(PYTHON_ROOT)/include/python2.7 -I$(BOOST_INC) -fPIC -c $(TARGET).cpp



brew でboost pythonをインストールする。

PythonからC++を呼び出せるBoost.Python。Ubuntuではすんなり出来たけど、Macではなかなかうまく行かない。コンパイルは通ってもimportすると、Fatal Python errorとか言われる。
結局
brew uninstall boost
brew install boost --with-python
でimportできるようになった。--with-pythonがないとコンパイル時の-lboost_pythonフラグに対して「boost_pythonなんてライブラリないよー」と言われる。

python3でやってみたいけど、今回はつかれたのでしばらくは2でいこう。

2014年5月4日日曜日

API Design for C++ を読んで学んだことをまとめておく。

順不同。詳しいことは本を買って読んで。
  • Virtual ClassのデストラクタはVirtualで宣言しておく。
    • 子クラスでのみ宣言すると、親クラスの型で変数を定義したときに困る。(P87)
    • メソッドとかは親クラスの型で戻り値を指定しても、子クラスを返すことができる。
  • クラスでメモリを扱う場合はCopy Constructor, Assignment Operator, Destructorの3つをセットで定義すべし。さもないとコンパイラが自動でShallow copyを行う。(P176)
  • getter/setterメソッドは何度も呼び出されるなら、inlineで宣言すればオーバーヘッドがなくなる。でもinlineの中にforとかが会った場合は、勝手にコンパイラがinlineでなくすときがある。小さくて、シンプルなものはinlineするとよい。

boost::tupleを使って2つの戻り値を返すときのメモ

boost::tupleを使って2つの戻り値を返すときのメモ。
作り方はboost::make_tuple(type,1 type2)
取り出し方はinstance.get<i>()

#include <iostream>
#include "eigen-eigen-6b38706d90a9/Eigen/Dense"
#include <boost/tuple/tuple.hpp>

using namespace Eigen;
using namespace std;

boost::tuple<Vector3d, Vector3d> func() {
    Vector3d v1 = Vector3d(1,2,3);
    Vector3d v2 = Vector3d(4,5,6);
    return boost::make_tuple(v1, v2);
}

int main() {
    boost::tuple<Vector3d, Vector3d> vs = func();

    cout << vs.get<0>() << endl;
    cout << vs.get<1>() << endl;
    return 0;
}



2014年5月2日金曜日

Makefileのメモ


以下はMakefileを学習したときのメモ
  • http://shin.hateblo.jp/entry/2012/05/26/231036を参考にした。
  • -oで実行フィアル名の指定。
  • -c オプションでコンパイルのみ実行。リンクはしない。
  • -gでデバッグ情報付加。-Wallでwarningをすべて表示する。
  • ターゲットを分けて書くことで、一部を更新したときに全部をコンパイルしなくて済む。
  • clean: でファイルを消去したりできるけど、このときcleanというファイル存在するとややこしくなるので、.PHONY: clean
  • headerファイルの更新を反映させるために依存ファイルに.hを加える。
  • $@でターゲット名を表す
  • $< で依存ファイルの先頭を表す。
  • $^で依存ファイルのリスト
  • suffix ruleを使えば、拡張子の同じものはまとめて記述できる。
最終的に以下のようになった。といっても上のリンクと同じ。

# Makefile
program = hello
objs = hello.o
CXX = g++
CFLAGS = -g -Wall

#.PHONY: all
#all: $(program)

#primary target
$(program): $(objs) 
    $(CXX) -o $@ $^

#suffix rule
.SUFFIXES: .cpp .o

.cpp .o:
    $(CXX) $(CFLAGS) -c $<

#headers
#hello.o: hello.h

.PHONY: clean
clean:
    rm -f $(program) $(objs)