Emacsの設定
Font設定
- RIcty-Diminisedを別途インストールしておく。
- Option-Set Default FontからGUIでフォントを設定する。
- scratchバッファで(frame-parameter nil 'font)でフォント名を確認する。
- その文字列を下記のように追記する。
(add-to-list 'default-frame-alist '(font . "-PfEd-Ricty Diminished-normal-normal-normal-*-15-*-*-*-*-0-iso10646-1"))
基本設定
(package-initialize) (setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/") ("melpa" . "http://melpa.org/packages/") ("org" . "http://orgmode.org/elpa/"))) ;; Preference & Misc settings (load-theme 'deeper-blue t) (tool-bar-mode 0) (setq inhibit-startup-message t) (setq make-backup-files nil) (setq ring-bell-function 'ignore) (show-paren-mode t) (setq frame-title-format "%f") (column-number-mode t) (line-number-mode t) ;; Key bindings (define-key global-map "\C-h" 'delete-backward-char) (define-key global-map "\C-z" 'undo) (global-set-key [C-tab] 'next-multiframe-window) (global-set-key [C-S-iso-lefttab] 'previous-multiframe-window) ;; font (add-to-list 'default-frame-alist '(font . "-PfEd-Ricty Diminished-normal-normal-normal-*-15-*-*-*-*-0-iso10646-1")) ;; Install use-package.el (when (not (package-installed-p 'use-package)) (package-refresh-contents) (package-install 'use-package))
拡張パッケージ
(Caskはプロキシ周りでハマるのでパッケージ管理は,package.elとuse-package.elに変更した。)
補完関係(company, irony(c/c++), jedi(python))
- 補間がauto-completeよりもcompanyの例を見るのでそっちにする.特に,C/C++ようにirony-modeも入れておく.Pythonにはcompany-jediを入れておく(jediは入れない).
- Caskでcompany, company-ironyを指定しておく.
- 初回起動時にはirony-install-server(c/c++), jedi:install-server(Python)を実行する.(この時にlibClangが無いとエラーになるので,libClang-devを入れておく.Macだとllvm --with-clangで入っている。Cmakeも必要なので注意。)
- elpyのコード内移動(定義元へジャンプなど)は内部ではjediを使っている。よって,elpyでコード内移動がうまく動いていないときはjediの設定が間違っている。M-x elpy-configでjediがちゃんと見つかっているかチェックする。また,Ubuntu18.04はpythonがpython2がまだデフォルトなので,RPCもpython3を指定するようにする。
スニペット
- 最低限の設定は下記。最近のバージョンはTABが展開キーだと指定する必要がある。
- 下記の設定だと標準のyas-installed-snippet-dir内のsnippetと、自分で追加したsnippet(.emacs.d/snippet)が使用される。
- 使い方としては、(キーバインドを変えているけど)、例えばpythonモードで、classでTABを打つとクラス定義のテンプレートを生成してくれるし、ifmでTABを押すとif name == 'main':を展開してくる。
- 新しくsnippetを生成するときは、'C-x i i'でYasnippet編集バッファに移動して、名前(ファイル名)と、キー(TABで展開する指定文字)を決めて、その後ろに展開したいコードを書く。C-c, C-cで保存する。モードとか、フォルダを聞いてくるので適時応える。
dotemacs (use-package)
(require 'yasnippet) (setq yas-snippet-dirs '("~/.emacs.d/snippets" yas-installed-snippets-dir)) (define-key yas-minor-mode-map (kbd "C-x i i") 'yas-insert-snippet) (define-key yas-minor-mode-map (kbd "C-x i n") 'yas-new-snippet) (define-key yas-minor-mode-map (kbd "C-x i v") 'yas-visit-snippet-file) (yas-global-mode 1) (custom-set-variables '(yas-trigger-key "TAB")) ;; Company (except for Python mode) (use-package company :commands global-company-mode :init (progn (global-company-mode) (setq company-global-modes '(not python-mode cython-mode sage-mode)) ) :config (progn (setq company-tooltip-limit 20) ; bigger popup window (setq company-idle-delay .3) (setq company-minimum-prefix-length 3) (setq company-echo-delay 0) ; remove annoying blinking (setq company-begin-commands '(self-insert-command)) )) ;; ;(define-key company-mode-map (kbd "TAB") 'company-complete) ;; (define-key company-active-map (kbd "TAB") 'company-complete-common) ;; (define-key company-active-map (kbd "M-h") 'company-show-doc-buffer)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Python ;; Python (use-package flycheck :ensure t :init (global-flycheck-mode t) :config (setq flycheck-python-flake8-executable "flake8")) ;; Standard Jedi.el setting (use-package jedi :ensure t :init (add-hook 'python-mode-hook 'jedi:setup) (setq jedi:complete-on-dot t) :config (use-package company-jedi :ensure t :init (add-hook 'python-mode-hook (lambda () (add-to-list 'company-backends 'company-jedi))) (setq company-jedi-python-bin "python"))) (use-package elpy :ensure t :init (with-eval-after-load `python (elpy-enable)) :config (progn ;; Use Flycheck instead of Flymake (when (require 'flycheck nil t) (remove-hook 'elpy-modules 'elpy-module-flymake) (remove-hook 'elpy-modules 'elpy-module-yasnippet) (remove-hook 'elpy-mode-hook 'elpy-module-highlight-indentation) (add-hook 'elpy-mode-hook 'flycheck-mode)) (elpy-enable) ;; jedi is great (setq elpy-rpc-python-command "python3") (setq python-shell-interpreter "ipython3") (setq python-shell-interpreter-args "-i --simple-prompt") (setq elpy-rpc-backend "jedi")))
Cask編(もう使っていないけどメモとして残しておく)
package.elではなく,caskかEl-docというので管理するのが主流らしい. caskを使ってみる.
インストール
$ curl -fsSkL https://raw.github.com/cask/cask/master/go | python # ubuntu $ brew install cask # OSX $ export PATH="/home/nobunaga/.cask/bin:$PATH"
初期設定と使い方
$cd .emacs.d $ cask init # 初期化時に一度だけ実行.Caskというファイルが生成される. $ cask install # Caskの指定にしたがって必要なパッケージをインストール. $ cask update # updateがあれば更新する.
Caskの書き方
- (depends-on "package-name") でpackage-nameのパッケージが.emacs.d/.cask以下に保存される
init.elの設定
- 下記を記入しておくと,Caskに書いたファイルを自動でrequireしてくれる.
(require 'cask "~/.cask/cask.el") ; Ubuntu (require 'cask) ; OSX (cask-initialize)
(global-company-mode 1) ;; c/c++ (add-hook 'c-mode-hook 'irony-mode) (add-hook 'c++-mode-hook 'irony-mode) (add-hook 'irony-mode-hook 'irony-cdb-autosetup-comile-options) (add-to-list 'company-backends 'company-irony) ;; python (require 'jedi-core) (setq jedi:complete-on-dot t) (setq jedi:use-shortcuts t) (add-hook 'python-mode-hook 'jedi:setup) (add-to-list 'company-backends 'company-jedi) ;; general (setq comany-idle-delay 0) (setq comany-minimum-prefix-length 2) (setq comany-selection-wrap-around t) ;; (setq company-idle-delay nil) ; 自動補完をしない (global-set-key (kbd "C-M-i") 'company-complete) (define-key company-active-map (kbd "C-n") 'company-select-next) (define-key company-active-map (kbd "C-p") 'company-select-previous) (define-key company-search-map (kbd "C-n") 'company-select-next) (define-key company-search-map (kbd "C-p") 'company-select-previous) (define-key company-active-map (kbd "<tab>") 'company-complete-selection)
Yasnippet
(require 'yasnippet) (setq yas-snippet-dirs '("~/.emacs.d/snippets" yas-installed-snippets-dir)) (define-key yas-minor-mode-map (kbd "C-x i i") 'yas-insert-snippet) (define-key yas-minor-mode-map (kbd "C-x i n") 'yas-new-snippet) (define-key yas-minor-mode-map (kbd "C-x i v") 'yas-visit-snippet-file) (yas-global-mode 1) (custom-set-variables '(yas-trigger-key "TAB"))
ODEのテンプレートプログラム
ODEのテンプレートプログラムで流れを確認しておく.
ヘッダや宣言
dobuleとfloatの描画ルーチンを自動でコンパチにする宣言を入れておく.
#include <iostream> #include <ode/ode.h> #include <drawstuff/drawstuff.h> // Doubleで扱う場合は描画ルーチンをD型にする. #ifdef dDOUBLE #define dsDrawBox dsDrawBoxD #define dsDrawSphere dsDrawSphereD #define dsDrawCylinder dsDrawCylinderD #define dsDrawCapsule dsDrawCapsuleD #define dsDrawLine dsDrawLineD #endif // Drawstuffで描画する際のテクスチャの場所を指定する. #define TEXTURES_PATH ("/usr/local/share/drawstuff/textures") #define DENSITY (5.0)
オブジェクトの構造体
剛体計算はBodyとMassで,衝突計算はGeomを使う.両方を使う場合はそれで一つのオブジェクトになるように構造体を宣言しておくと便利.
各種オブジェクトの描画ルーチンセット
これは問題関係なく共通で使えると思う.GeomからPosition/Rotationとかの情報は取れるので,GeomIDを渡すようにする. でも,実は下のはちゃんと動かない...シリンダーとカプセルのIDがどっちも3でうまくスイッチできない...なぜだろう?
// Both of the Cylinder and Capsule class id is 3 ?? void drawObject(dGeomID gID,float red,float green,float blue) { dsSetColor(red,green,blue); //std::cout << dGeomGetClass(gID) << std::endl; switch (dGeomGetClass(gID)) { case dSphereClass: { dsDrawSphere(dGeomGetPosition(gID),dGeomGetRotation(gID),dGeomSphereGetRadius(gID)); break; } case dBoxClass: { dVector3 length; dGeomBoxGetLengths(gID, length); dsDrawBox(dGeomGetPosition(gID),dGeomGetRotation(gID),length); break; } case dCylinderClass: { dReal r,length; dGeomCylinderGetParams(gID, &r,&length); dsDrawCylinder(dGeomGetPosition(gID),dGeomGetRotation(gID),length,r); break; } case dCapsuleClass: { dReal r,length; dGeomCapsuleGetParams(gID, &r,&length); dsDrawCapsule(dGeomGetPosition(gID),dGeomGetRotation(gID),length,r); break; } } }
Drawstuffのセットアップ
Drawstuffを管理するオブジェクトdsfFunction(ここではfnとしている)を定義しておいて,下記でセットアップする.
void setDrawStuff() { fn.version = DS_VERSION; fn.start = &initDs; //初期化関数へのポインタ fn.step = &simLoop; // 無限ループで毎回実行する関数ポインタ fn.command = command; // キーボード入力を処理する関数ポインタ fn.stop = NULL; // 停止時の処理の関数ポインタ fn.path_to_textures = TEXTURES_PATH; }
dsFuntion.startにセットする関数ではカメラ視点の設定などを実行する.xyzはカメラの絶対座標,hprはヨー,ピッチ,ロール.右手系にHPRのそれぞれの回転角度があるとして,それぞれの方向にどれだけ回すかを与える.注意点として,DrawStuffの角度は度(degree)で与えられる.他は一般的にラジアンで統一されている場合があるので注意. 下の例だとX軸方向に180度,つまり画面の奥側を向いて,Y,Z軸には回転しない.
static void start() { static float xyz[3] = {10, 0, 1.0}; static float hpr[3] = {-180, 0, 0}; dsSetViewpoint (xyz,hpr); }
キーボード入力を処理する関数は例えば下記.
void command(int cmd) { switch (cmd) { case 'r': v += 0.5; break; case 'l': v -= 0.5;break; default: std::cout << "WARNING: unknown key input" << std::endl; } }
fn.stepはDrawstuffの中の無限ループ内で実行される関数をセットする.逆に,Drawstuffを使わないで(描画しないで)ODEのシミュレーションをする場合は,この関数を別途無限ループの中に入れるんだ. ジョイントを制御をする場合のcontrolに関しては,その項目を参照.
static void simLoop (int pause) { if (!pause) { dSpaceCollide(space,0,&nearCallback); // 衝突計算 dWorldStep(world,0.1); // タイムステップを1時刻すすめる(動力学計算) dJointGroupEmpty(contactgroup); // 衝突計算のジョイントグループを初期化 control(); // ジョイントを制御する場合はその関数を呼び出す. } // 結果を描画する(Drawstuffを使わない場合は飛ばす) drawObject(base.geom, 0,0,05); // STDOUTに情報を出力する場合 std::cout << "angle:" << dJointGetHingeAngle(joint); }
衝突計算をする場合
衝突計算自体はdSpaceCollideで実行される.これは各オブジェクトが衝突するかどうかを判定してくれる.この関数にはnearCallBack関数を登録する.これは,2つの物体が衝突する(かも知れない)と判定された時に呼び出される関数.このコールバック関数の中で接触点をジョイントとして登録する.ジョイントはすなわち拘束条件だ.摩擦係数や反発係数は別途PRAM_BOUNCEなどでマクロ定義しておく.
static void nearCallback(void *data, dGeomID o1, dGeomID o2) { const int N = 10; dContact contact[N]; int isGround = ((ground == o1) || (ground == o2)); int n = dCollide(o1,o2,N,&contact[0].geom,sizeof(dContact)); if (isGround) { for (int i = 0; i < n; i++) { contact[i].surface.mode = dContactBounce|dContactSoftERP|dContactSoftCFM; contact[i].surface.bounce = PRAM_BOUNCE; contact[i].surface.mu = PRAM_MU; contact[i].surface.soft_erp = PARAM_SOFT_ERP; contact[i].surface.soft_cfm = PRAM_SOFT_CFM; dJointID c = dJointCreateContact(world,contactgroup,&contact[i]); dJointAttach (c,dGeomGetBody(contact[i].geom.g1),dGeomGetBody(contact[i].geom.g2)); } } }
ロボット定義
シミュレーション対象となるロボットの定義は,(1)剛体(Body)の定義,(2)それに質量(Mass)を付けて,(3)幾何(Geom)を付ける.Geomは衝突計算をしない場合は不要.これを愚直に書いて行くんだ. 例えば,直方体に円柱の車輪をつける例.ほとんどそのままだけど,円柱は回転させる必要があるので,回転行列rotを定義して,回転させたい軸と回転角に対する回転行列をdRFromAxisAndAngle([0,1,0]の軸の周りに,[0,1,pi/2]回す)で作っておいて,dBodySetRotationで回転する. また,車輪を回したいのでジョイントを付ける.ODEのジョイントはモータを内蔵しているので,それを回すことで車輪を回せる.Axisがジョイントの軸方向,Anchorが軸の場所.
void makeRobot() { dMass m; dMassSetZero (&m); //basement base.body = dBodyCreate (world); dMassSetBox (&m,DENSITY,sides[0],sides[1],sides[2]); dBodySetMass (base.body,&m); dBodySetPosition (base.body,0,0,radius+sides[2]/2); base.geom = dCreateBox(space, sides[0], sides[1], sides[2]); dGeomSetBody(base.geom,base.body); dMatrix3 rot; // wheel dRFromAxisAndAngle(rot,0,1,0, M_PI/2.0); wheel.body = dBodyCreate (world); dMassSetCylinder(&m,DENSITY,1,radius,width); dBodySetMass (wheel.body,&m); dBodySetPosition (wheel.body,0.0,0,0.3); wheel.geom = dCreateCylinder(space, radius, width); dGeomSetBody(wheel.geom,wheel.body); dBodySetRotation(wheel.body,rot); joint = dJointCreateHinge(world, 0); dJointAttach(joint, base.body, wheel.body); dJointSetHingeAxis(joint, 1, 0, 0); dJointSetHingeAnchor(joint, 0.0, 0, 0.3); }
Main関数
ここまで出来たら,最後にメイン関数を書いて終わりだ.world,spaceなどをグローバル変数で渡すような例が多いようなので,グローバル変数として扱うなら適切な場所で定義しておく.
// Global variables static dWorldID world; static dSpaceID space; static dGeomID ground; static dJointGroupID contactgroup; dsFunctions fn; int main (int argc, char **argv) { setDrawStuff(); dInitODE(); world = dWorldCreate(); dWorldSetGravity(world,0,0,-3.0); dWorldSetERP(world, 0.5); dWorldSetCFM(world, 1e-05); space = dHashSpaceCreate(0); contactgroup = dJointGroupCreate(0); ground = dCreatePlane(space,0,0,1,0); makeRobot(); // initial noise dsSimulationLoop (argc,argv,600,400,&fn); dWorldDestroy (world); dJointGroupDestroy(contactgroup); dSpaceDestroy(space); dCloseODE(); return 0; }
制御する
制御をするには力やトルクを与える.ここで重要なのは,力やトルクはシミュレーションの更新ごとに0に初期化されるので,継続的に力を加える場合は継続的に力を加える.一回与えても,その一回で消えちゃうよ. 力やトルクをdBodyAddForceのようにBodyに直接力を加える場合の他に,ODEのジョイントはモータになっていて,そのモータにトルクを与えることができる.実際にはジョイントにはトルクではなく速度を与える.
シミュレーション上のTips
描画をしない
デバッグが終わったら描画をせずにシミュレーションだけをしたい場合もある.その場合はsimLoopの描画の箇所をスキップするようにして,Drawstuffのループではなくて自分のWhile(0)ループの中でsimLoopを回す. 他にも,ctrl-tでテクスチャをOffにできる.ウィンドウサイズを変えたりでもシミュレーション速度が変わる.
制御コマンド
デフォルトでいくつかの制御キーコマンドが定義されている. - ctrl-t:テクスチャをOn/Off - ctrl-s:影のOn/Off - ctrl-p:シミュレーションの一時停止 - ctrl-o:1ステップ実行
リスタート
強化学習のシミュレーションなどをやる場合には何度もシミュレーションを繰り返す.やり方は,一旦Body, Geom, Joint,JointGroupを全部消して,再度作りなおす.
void makeRobot() { ... // 上のと同じでいい } void deleteRobot() { dJointDestory(joint); // jointを削除 dBodyDestory(body); // bodyを削除 dGeomDestory(geom); // geomを削除 dJointGroupDestory(contactgroup); } void restart() { deleteRobot(); contactgroup = dJointGroupCreate(0); makeRobot(); }
C++プログラミングのメモ
Last update 16/07/25
概要
- プログラミングをする上で,変数名の付け方とかprivate/publicの使い分けとかを勉強したことをまとめておく.
API化/OOPプログラミング
- 目的は実装を隠すことで,利用者に影響を与えずに内部を常に改変できるようにすること.これが一番の理由.
- それによってモジュール化が促進される.どれを外に見せるのか?を常に考えるので自然と疎結合になるようにプログラムされる,ってことかな?
カプセル化の方法
- 宣言と定義は異なる.宣言はコンパイラに名前と型を知らせる.その実装やメモリ割り当てが定義だ.
- 物理的な隠蔽方法として,宣言をヘッダファイルに,定義をソースファイルに書くようにする.例外はテンプレートを使う場合とインライン化の場合(後述).
- メンバ変数はprivateにすること.publicもprotectedもだめだ.setter/getterを定義する.当然だが,setter/getterを本当に定義するべきかを考える.パフォーマンスもほぼ有意差は出ない.
- protect指定をしてサブクラスにメンバ変数を公開すると,ユーザがそのクラスを継承したサブクラスを作ると実装を公開することになってしまう.継承を使うとオブジェクト指向設計のカプセル化の利点が大幅に減少する.(継承はOOPの根本ではない,根本はカプセル化ってことなのかな?)
- クラスは何をするか?を定義するべきであり,どうするか?を定義するのではない.これがAPI設計の基本原理だ.これに従いメンバ関数もprivateに移す.
- でも結局privateをヘッダファイルに書いていると中身をユーザに見られてしまう.(もっと言えば#define private public)とかすると公開になってしまう.そんな時の対処法がPimplイデオム.C++プログラマなら絶対に覚えておくこと.
- メンバ変数,メンバ関数だけじゃない,クラスだって不要なクラスはユーザに見せるべきではない.公開するクラスと公開しないクラスを意識する.
- 約束(公開API)は最小にする.機能の追加はできても,削除は出来ないのだ.やってしまいがちなのが将来を見越した余分な抽象化の提供.実際はほとんど必要にならずに足かせになる事が多い.重複した機能を持つ別のAPIを作っちゃダメ.
- でも,最小化すると使いにくい.便利な(最小コア関数を複合した高機能な)関数を別途用意したくなるだろう.そんな時は,それを最小コアのクラスと同じクラスにせずに別クラスにする.OpenGLとGLUTが良い例.
- APIを誤用させないようにする.例えば,Bool型の引数をenumで宣言しておく.trueとDATA_VALIDだと後者の方が意味を考えてユーザは使う.これは特定の引数をクラス化することにも応用される.とにかく,ユーザに何を書いているのか意識させることと,コンパイル時にエラーチェック出来るようにするんだ.
Cでの場合
- namespaceが無いので名前が衝突しないようにプレフィックスを付けるか,staticでファイルローカルにする必要がある.
- 最大のメリットは既存のC財産と共有できること.
- オペークポインタ(オペーク:未定義の,つまり未定義の型を指すポインタ)を使って実装を隠蔽せよ.ヘッダファイルは公開されるんだぞ.
// MyClass.h // MyClassの中身を書かない. typedef struct MyClass *MyClassPtr; void MyClassFunc(MyClassPtr myclass);
// MyClass.cpp // こっちに定義を書いておけばhogeがあることなどは公開されない. struct MyClass { int hoge int *foo }
- C++から呼び出せるようにする手間は小さいから,Cを書く場合でもやっておくこと.やることは,extern “C"で囲むこと,C++のコンパイラを通ること(予約後の違いなど).
- extern “C"の意味はC++コンパイラに「これはCスタイルですよ」と教えること.具体的に何を教えているかというと,リンケージ情報を教えている.C++はオーバロードを許すから同じ名前の関数が複数存在しうる.そのため,名前が引数の型などによって符号化されている,これをマングルするという.この違いを吸収するのがextern "C” {}.
- 具体的には,Cコンパイラでは無視して,C++コンパイラでは読むようにするために下記の方法をとる.
#ifdef __cplusplus extern "C" { #endif ... ... ... #ifdef __cplusplus } #endif
オブジェクト指向C++
- メリットは手続きの集合ではなく,モノとそれに対する操作の集合という形で問題をモデル化できること.これは人間が直感的に考えるやり方と相性が良い.
- デメリットは難しいこと.is-aの関係でもないのに不要に継承したり,基底クラスのデストラクタのvirtualとか.
テンプレートベース
c++よく忘れること
概要
- 久しぶりにC++を書こうとすると(というよりも読もうとすると)忘れてしまっていてリハビリするのがコピーコンストラクタ,this, Template, 参照渡し,仮想関数, オーバライド,スマートポインタ。忘れがちな点を纏めておく。
符号なし整数と符号あり整数の計算
- 符号なし(unsinged int)と符号あり(int)の足し算をすると,intが暗黙のキャストをされて意図しない値(多くの場合は負の数が巨大な正の数)に変換されてしまう。
unsigned a = 1; int b = -1; cout << a + b << endl; // 0じゃない!!
enum class
enum SIGNAL {BLUE, YELLOW, RED}; int BLUE; // エラー
staticキーワード
- キーワードstaticはローカル変数につくのか,グローバル変数,関数に付くのか,メンバ変数かメンバ関数か?で意味が大きく異なるので混乱する。C++のstaticは多義語なので,個人的には久しぶりに見ると混乱するキーワード1位(クラス変数が初期化できない,という罠もあるし)。
staticローカル変数
- statiを付けて宣言したローカル変数は1度だけ初期化され,関数呼び出し終了後も状態を保存する変数となる。
int f(){ static int x=0; return ++x; }; int main(int argc, char* argv[]) { cout << f() << endl; // 1 cout << f() << endl; // 2 cout << f() << endl; // 3 return (0); }
グローバル変数/関数
- staticを付けた変数,関数はファイルにローカルになる。よって,複数のファイルで同じ名前の関数名を使いたい時(そんなことは良くないが)に,各変数,各関数にstaticを付けておけば,ファイルローカルになるので,名前衝突は発生しない。
// file1.cpp int x=0; // file2.cpp static x=1; // staticが無いと変数名'x'が衝突してエラー。
メンバ変数/メンバ関数
- staticを付けるとクラス変数(インスタンス変数ではない),クラス関数になる。
- ここで注意なのが,クラス変数は宣言時に初期化が出来ない!!。(なんでなんだろう??)
関数仮引数の書き方
- 値渡し,ポインタ渡しに加えて,参照渡し(&),右辺値参照渡し(&&)の4パタンがある。それにconstが付くので8パタンがある。
値渡し
- 値のコピーが渡されるので,関数内で値を更新しても,呼び出し側の変数は更新されない。
- constを付けると更新するコードのコンパイルが出来ない。
- そもそも,更新後の値が必要ないから値渡ししている変数にconstを付ける意味ってあるのかな??,と思ったが更新しないものを(意図せずに)更新している,ということをコンパイル時に明確に気づくためには意味があるのかも知れない。
int f(int x) { return ++x; } // xはコピーなので引数は影響を受けない。 int f(const int x) { return ++x; } // コンパイルエラー
ポインタ私
- ポインタ変数pに’*’を適用するとポインタを剥がせる(アドレスpから値を取り出せる)。
- ポインタ変数のconstは少し注意が必要。ポインタが指す先の値が変更不可なだけで,ポインタ自体の変更は可能。ポインタ自体も変更不可にしたい場合には,癖のある書き方(下の例を参照)をする。
int f(int* x) { return ++(*x); } int f(const int* x) { return ++(*x); } // コンパイルエラー int f(const int* x) { int* y=++x; // ポインタ自体は変更可能 return(0); // } int f(const int* const x) {...} // ポインタが指す先もポインタも変更不可にする場合
参照渡し
- ほとんどポインタと変わらない(表記が異なっていて,定義時に初期値が必要な点がポインタとの違い)。
- constを付けないとリテラルは渡せない。(というか参照は変数の参照というのが使い方なんだからリテラルを渡すことはあまり無いのかな??)
int f1(int& x) { return ++x; } //引数も更新される int f2(const int& x) {return ++x} // コンパイルエラー f1(10) // constがついていないとリテラルを渡すことはできない f2(10) // constがついていれば渡せる
右辺値参照
- もう少し頭の中の整理が必要だ。moveとか含めて最近(と言ってもC++11からあるのか・・・)。
コピーコンストラクタと代入演算子のオーバーロード
- C++では関数にオブジェクトを値渡しで渡す場合,宣言時の初期化,返り値とした場合にコピーコンストラクタが呼ばれる.
- 値渡しじゃなく,参照渡し(リファレンス&,ポインタ*)の場合はコピーコンストラクタは呼ばれない(コピーしないんだから).
- 変数宣言時の初期化のときの"="はコピーコンストラクタ呼ばれる(だって「初期化」だから)けれど,それ以外の代入のとき代入演算子=が呼ばれる.だから,自前クラスに=を使う場合はコピーコンストラクタの定義と代入演算子のオーバーロードが必要.
- 返り値にした場合もコピーコンストラクタが呼ばれるのは忘れやすいので注意.
- わざわざあるコピーコンストラクタが存在する理由は,ポインタを持っているオブジェクトをコピーされると,そのコピーされたオブジェクトがスコープを抜ける時にデストラクタが呼ばれて,もとのオブジェクトのポインタを解放してしまうから.例えば,クラスの中でメモリ参照をしている(newしている)ような場合,値渡しで単純にコピーするとそのポインタがコピーされる.そして,そのオブジェクトが関数を抜ける時にデストラクタが呼ばれてしまうと,もともとの参照していたポインタも解放されてしまう.(関数呼び出し元で再度デストラクタが呼ばれると2重解放になってエラー発生して止まる)
- 基本的には代入演算子はオーバロードして,コピーコンストラクタを書くようにする.ポインタをメンバ変数に持っている場合は必須.
#include <iostream> #include <string> using namespace std; class TestClass { public: TestClass(){ // Default constructor cout << "Default constructor is called." << endl; } TestClass(const string name) : name_(name){ // Custom constructor cout << "Custom constructor is called, I am name=." << this->name_ << endl; } TestClass(const TestClass & c){ // Copy constructor name_ = c.name_ + "_copy"; cout << "Copy constructor is calld, I am name=." << this->name_ << endl; } string getName() {return name_;} private: string name_; }; void value_pass_func(const TestClass c) { return; } void ref_pass_func(const TestClass & c) { return; } void pointer_pass_func(const TestClass * c) { return; } int main(void) { TestClass c0; // default constructor is called. TestClass c1 {"hoge"}; // custom constructor is called. TestClass c2 = c1; // copy constructor is called. cout << "value_pass_func is called," << endl; value_pass_func(c2); cout << "ref_pass_func is called," << endl; ref_pass_func(c2); cout << "pointer_pass_func is called," << endl; pointer_pass_func(&c2); return 0; }
this
- メソッド呼び出しの際には暗黙で呼び出し元のオブジェクトのポインタが渡される.それがthis.pythonで言うところのselfなのかな.
- メンバ関数を普通に使用する分には省略する.
- 使いみちとしては,メンバ関数ではない関数(クラスの外にある関数)にオブジェクトを渡す時に使う.
Template
- 型が違うだけで他は同じクラス,関数を一つにまとめる.具体的な型は呼び出し側で指定して,コンパイラはコンパイル時に型を埋め込む.(当然だけど動的言語みたいに実行時に型を解決している訳じゃない.)
- 同様のことは継承(多態性)でも出来る.違いはtemplateはコンパイル時に型を解決しているのに対して,継承は実行時にvtable(virtualな関数のテーブルをオブジェクトは持っている)を手繰って実行するべき関数を検索している.よって,継承のほうが実行コストが高い.templateは型付きの関数をコンパイル時に自動生成しているようなもの.但し,templateはコンパイル時に型を特定する必要があるが故に,次の通りヘッダに実装を書く必要があって汚くみえる.
- テンプレートを使う場合はクラスの実装をヘッダファイルに記述する.これは分割コンパイルはあくまでもソース単位で実行されるため,テンプレートの実装”だけ”を書いたソースをコンパイルしても,それが使われていなので実態が生成されないため.よって,ヘッダにしておいて,そのテンプレート関数なりクラスを使うソース(は当然それをインクルードしていて,型を指定する記述が登場する)に渡すのだ. (補足として,Cではヘッダファイルのインクルードは極端に言えばただの置換で,インクルードしたファイルの内容がそこに書き込まれると思えば良い.ソースファイルのコンパルはその置換後のファイルに対して実行される.これがヘッダファイルだけのライブラリが使われる理由.つまり,外部ファイルとかなく,ヘッダだけで真に完結する.コンパイル時間が大きくなるけどプリコンパイルを使うと避けられる.)
- テンプレートのスコープはtemplate文の次の宣言文の中だけ.
#include <iostream> #include <string> using namespace std; // この関数が型Xを使うことを宣言 template <class X> X add(X a, X b) { return a+b; } int main() { cout << add<int>(1, 2) << endl; cout << add<float>(1.0, 2.0) << endl; cout << add<string>("Oda", "Nobunaga") << endl; return 0; }
オーバーライド
- 親クラスのメンバ関数を子クラスが上書きすることができる.
- virtualを付けなくてもできることに注意.じゃあvirtualの意味は?というのは下を参照.
- 演算子の再定義や,引数の異なる関数を定義することはオーバロード(多重定義).
virtual
- C++では子クラスは親クラスのメソッドと同名のメソッドを定義可能.
- virtualと書くのは基底側だけでも良いけど,分かりやすいように継承側でも書く.
- virtualを付与すると実行時に型を解決して,どの関数を実行するのかを検索するので,多少はオーバヘッドがある.
- 親クラスでは中身が無い,つまり子クラス側で実装をお願いする関数を純粋仮想関数と呼び,純粋仮想関数を持つクラスを抽象クラスと呼ぶ.抽象クラスは実態を作れない.
class Hoge { public: virtual void func(void) = 0; // 純粋仮想関数 };
- virtualを付ける場合と付けない場合の違いは,ポインタ経由でメソッドを呼び出す時に現れる.C++では親クラスのポインタに子クラスのアドレスを入れることができて,その時に(おそらく)意図と異なる動作になってしまう.つまり,親クラスのポインタに子クラスのアドレスを入れて,親と子で同名のメソッドをポインタ経由で呼び出した時にどうなるか.virtualが付いていれば子クラスのメソッドが呼ばれて,付いていなければ親クラスのメソッドが呼ばれる.
#include <iostream> using namespace std; class Hoge { public: void non_virtual_func (void) {cout << "I am Hoge (non_virtual)" << endl;}; virtual void virtual_func (void) {cout << "I am Hoge (virtual)" << endl;}; }; class Foo : public Hoge { public: void non_virtual_func (void) {cout << "I am Foo (non_virtual)" << endl;}; virtual void virtual_func (void) {cout << "I am Foo (virtual)" << endl;}; }; int main() { Hoge *h1 = new Hoge(); h1->non_virtual_func(); // I am Hoge (non_virtual) h1->virtual_func(); // I am Hoge (virtual) Hoge *h2 = new Foo(); h2->non_virtual_func(); // I am Hoge (non_virtual) !! h2->virtual_func(); // I am Foo (virtual) return 0; }
関数オブジェクト(ファンクタ)
- Cでは関数ポインタを使って関数に関数を渡せた.同じような目的で使えるのが関数をオブジェクト化した関数オブジェクト.
- テンプレートと組合せて使われる例が多いけれど,それは(おそらく)STLのVectorとかに渡す場合を想定しているから.ファンクタ自体はテンプレートと関係は無い.
- 実装はoperator()をオーバーロードする.
#include <iostream> using namespace std; template <typename T> class MyAdder { public: T operator() (const T& a, const T& b) { // functor return a+b; } }; int main(void) { MyAdder<double> double_adder; MyAdder<int> int_adder; cout << double_adder(2.0, 1.2) << endl; cout << int_adder(2, 1) << endl; cout << int_adder(2.3, 1) << endl; // Warning }
スマートポインタ
- new/deleteの面倒さと危険さを回避するために導入された.
- auto_ptrは使うな,unique_ptrを使え.
- unique_ptrはスコープからでると自動で削除される.shared_ptrは参照数をカウントしていて,参照が0になったら削除される.
unique_ptr
- C++14以降はmake_uniqueで見た目がすっきりする.
#include <memory> std::unique_ptr<Class_name> p(new Class_name(arg*)); // c++14以前 std::unique_ptr<Class_name[]> p(new Class_name[N]); // N個の配列として渡す. auto v = std::make_uniqe<Class_name>(arg*); std::unique_ptr<Class_name[]> v_arr = std::make_uniqe<Class_name>(size); // 配列でかつ,引数渡したいときは?
vector
- 初期化方法と,emplace_back()とpush_back()の違いを忘れてしまう.emplace_back()はクラスのオブジェクトをvectorに入れる場合に使う.コンストラクタの引数を渡したいというのと,コピーコンストラクタのコストを下げるため.
#include <iostream> #include <vector> #include <algorithm> #include <functional> using namespace std; int main() { ////////////////////////////////////////////////// // Construction // - initialize with size and init. value vector<int> v0(10, 0); // {0,0,0,0...0} // - initialize with a vector vector<int> v1(v0); // - initializer list (from c++11) vector<int> v2{0,1,2,3,4,5,6,7,8,9}; // - initialize with an array int arr[] = { 100, 600, 300, 500, 400, 200 }; vector<int> v3(arr, arr + (sizeof(arr)/sizeof(int))); // - initialize with iota vector<int> v4(10); iota(v4.begin(), v4.end(), 5); // {5,6,7,...14} ////////////////////////////////////////////////// // Add element // - If add element is an object, use emplace_back() // which can pass the constructer args. v0.push_back(10); // v0={0,0,0...0,10} // vobj.emplace_back(arg1, arg2,...) ////////////////////////////////////////////////// // Algorithm //sort(viter, vend); // ranged for for (auto d : v4) cout << d << endl; return 0; }
Common lispの開発環境
動機
- 遺伝的プログラミングの勉強をしているとLispの例が多いから.人工知能系の研究で従来から使われている理由(Cに比べての高級性)は最近の言語には劣るから意味ないと言われてもいるようだけど,少し本を読んだ限りでは高級性以外の関数型であること,さらにプログラムがデータであることによる自己増殖に可能性を感じたから.
- 世の中のすごいプログラマの多くが良いと言うんだからそこには何かあるんだろうという期待.
環境構築
- 色々と処理系があるけど,フリーだとClispかSBCLかClozureというのが良く使われるようだ.コンパイルまでしてくれるSBCLを使うことにする.また,環境はSlimeというのがEmacsで使えるようなのでそれを使う.gemに相当するものとしてquicklispというのがあるようなのでそれも入れる.初回にSBCLを起動してquciklispをインストールしてInitファイルに書き込んでおく.
$ sudo apt-get install sbcl slime cl-quicklisp # インストール $ sbcl # SBCLを起動して初期設定 * (load "/usr/share/cl-quicklisp/quicklisp.lisp") * (quicklisp-quickstart:install) * (ql:add-to-init-file) * (ql:update-all-dists) ; 定期的にリポジトリをアップデートする
- 補完もしてほしいので、slime-companyを入れる。Cask(MELPA)から入るので簡単。設定も下記を.emacsに書くだけ。
(slime-setup '(slime-fancy slime-company)
Emacsの設定ファイルに下記を追記
(setq slime-net-coding-system 'utf-8-unix) ; common-lisp system setting, SBCL:sbcl, Clozure:ccl, clisp: (setq inferior-lisp-program "sbcl") (setq load-path (cons (expand-file-name "/usr/share/emacs/site-lisp/slime") load-path)) (require 'slime-autoloads) (slime-setup '(slime-repl slime-fancy slime-banner slime-company))
Hyperspec
w3mで見れるようにすること,ローカルのファイルを参照するようにすること. - UbuntuだとHyperspecはapt-getで入る.MacならLispworksからTarを落としてくる. - apt-getでw3m, w3m-elを入れておく. - 下記をinit.elに記載.
(require 'w3m) (setq browse-url-browser-function #'w3m-browse-url) ;(global-set-key (kbd "C-c h") 'hyperspec-lookup) (setq common-lisp-hyperspec-root (concat "file://" (expand-file-name "/usr/share/doc/hyperspec/")) common-lisp-hyperspec-symbol-table (expand-file-name "/usr/share/doc/hyperspec/Data/Map_Sym.txt"))
開発手順
- Emacsを起動してSlimeを起動.M-x slime.
- Lispファイルを編集.
- 関数の上でC-c C-cで関数定義をSlimeに送る.
- C-c C-zでSlimeバッファに飛ぶ.
- REPL上で動作確認をする.
- 関数が正しく動作するまで2に戻る.
他に開発中に便利なコマンドは下記.
C-c C-c: 関数のコンパイル C-M-x : 現在のトップレベル関数の評価 C-c C-k: バッファ全体をコンパイル C-c C-z: REPLに飛ぶ C-c C-r: 領域の評価 C-x C-e: 直前のS式の評価 (C-xなので注意) M-. : シンボルの定義に飛ぶ M-, : ジャンプまでに戻る M-p : 前の履歴 (コンパイル情報の参照) M-n : 次の履歴 M-x slime-restart-inferior-lisp: REPLを再起動
slimeによるデバッグ
- エラーが発生したら,”:”でslime-evalに入って任意の式をグローバル環境で評価できる.道標に"e"はフレーム内で評価できる.
- "n"/"p"でフレームを降りたり上がったり.一番上の0番がスタックフレームのトップ(つまりそこでエラーが発生している).
- "v"でフレームに対応するコードを表示.
- "t"でローカル変数を表示.