2009年10月17日土曜日
C++のコピーコンストラクタ
コピーコンストラクタが役に立つのは以下の3つの場合である.
・宣言文でオブジェクトを使用して,他のオブジェクトを宣言する
・関数に仮引数としてオブジェクトを渡すとき
・関数の戻り値として使用する一時オブジェクトを作成したとき
例えば,仮引数にクラスcarのオブジェクトを渡す
void printobj(car obj){ ... }
の場合は,コピーコンストラクタなしでは,正確なコピーを行うので,
ポインタ変数があると,関数を抜けるときに,デストラクタが呼ばれて
メモリの解放が行われる.
さらに関数を呼んだ側でデストラクタを呼ぶと,ポインタ変数の指す先のメモリは
既に解放された状態であるから,ここで二重解放なってしまう.
二重解放がまずい理由は,
ttp://d.hatena.ne.jp/aki-yam/20081205/1228490763
によると,2つのポインタ変数が同じアドレスを指しているときに
その症状が現れるということらしい.
int *p1 = new int;// エラーチェックは省略
*p1 = 100;
delete p1;
int *p2 = new int;
delete p1;//ここで二重解放
*p2 = 200;
ここで p1 == p2だとすると厄介ということである.
他にも例があるかもしれないが,これだけでも不味いということが
分かったのでよしとしよう.
正確なコピーを作らせないように挙動を制御するのが,
コピーコンストラクタの役割である.
2009年10月15日木曜日
C++のアクセス制御
public,private,protected,virtualあたりを
自分なりにまとめてみた.継承に注目して書いてみる.
間違っていたら是非指摘していただきたい.
publicで継承
基本クラスのアクセス制御が、そのまま派生クラスに引き継がれる.
基本クラスのpublicな部分は、派生クラスでもpublicのまま.
基本クラスのprivateもprotectedな部分もそのままになる.
privateで継承
基本クラスを全部privateにしてしまう.
派生クラスを呼び出す外部からは、アクセスできない.ただし,
派生クラス自身から基本クラスのpublicな部分へのアクセス自体は可能である.
基本クラスのprotected
基本クラス自身と派生クラス以外からはアクセスできない.
virtual
B <- d1 <- D(Bが間接基本クラスで,Dが派生クラス)
B <- d2 <- D
と継承すると,DからBを用いようとすると,どちらから継承した
Bを用いるのか分からず,エラーになる.それを避けるために,
virtualをつけて,ひとまとめにして曖昧さを避けることができる.
2009年10月8日木曜日
RとC言語で関数ポインタ
具体的な問題設定は,複数の入力文字列に対応させて,
データの入ったテーブルの各列で統計量を計算するというものでした.
(例えば1列目は最小値を、2列目は平均値...のように列ごとに設定する)
まずはRで書いた,文字列で統計量を計算する関数を返す
ソースコードを載せます.
setfunc <- function(type){ if( type == "min" ){ return(min) }else if( type == "max" ){ return(max) }else if( type == "median" ){ return(median) }else if( type == "mean" ){ return(mean) } } d <- c(1:10,rep(10,5)) d f <- setfunc("mean") f(d) f <- setfunc("max") f(d) f <- setfunc("median") f(d) f <- setfunc("n") f(d)この実行結果は下記のようになる。
> d <- c(1:10,rep(10,5)) > d [1] 1 2 3 4 5 6 7 8 9 10 10 10 10 10 10 > f <- setfunc("mean") > f(d) [1] 7 > f <- setfunc("max") > f(d) [1] 10 > f <- setfunc("median") > f(d) [1] 8 > f <- setfunc("n") > f(d) エラー: 関数 "f" を見つけることができませんでしたこれをCで実装したらどうなるかとhttp://ujihisa.blogspot.com/ を書いているujihisaさんとskypeで2時間余り議論して,ようやく動いたのがこれ. medianについては未実装で,実行結果もありませんがご容赦を.
#include <stdio.h> #include <string.h> double mean(double d[],int n) { int i; double sum = 0.0; double *dp; for( i=0 ; i<n ; i++ ){ sum += d[i]; } return sum/n; } double median(double d[],int n) { return -1; } double min(double d[],int n) { int i; int idx = 0; // 最小値の添字 for( i=0 ; i<n ; i++ ){ if( d[idx] > d[i] ){ idx = i; } } return d[idx]; } double max(double d[],int n) { int i; int idx = 0; // 最大値の添字 for( i=0 ; i<n ; i++ ){ if( d[idx] < d[i] ){ idx = i; } } return d[idx]; } double (*p[])(double [],int)={mean,median,min,max,NULL}; char *cp[]={"mean","median","min","max",NULL}; double (*setfunc(char *str))(double d[],int n) { int i; for( i=0 ; cp[i] != NULL ; i++ ){ if( !strcmp(cp[i],str) ){ return p[i]; } } printf("error in setfunc\n"); return NULL; // 文字列に一致したものがなかった } int main(void) { double (*f)(double d[],int n); double a[10]={0,1,2,3,4,5,6,7,8,9.0}; f = setfunc("mean"); printf("%lf\n",f(a,10)); f = setfunc("min"); printf("%lf\n",f(a,10)); f = setfunc("max"); printf("%lf\n",f(a,10)); return 0; }
すぐに思いつく改良点は,
・*pと*cpをバラバラではなく,構造体でまとめて持たせる
・長さについては,入力エラーチェックが必要.
といったところでしょうか.また,改良して議論したいと思います.
今日の結論としては,Rと比較すると,C言語の場合はプログラマがポインタをいちいち作らないといけないので,非常に面倒で,かつ間違えやすいということです.
言語の特徴を知って,実装に最適なものを選べということですね.
追記(2011/04)
上記の改良、および中央値の実装を行いました。
http://entertainment-lab.blogspot.com/2009/12/rc.html
2009年10月5日月曜日
sweep関数に関しての調査
sweep関数の挙動が文章の説明だけでは分からなかった.
ぐぐってみるとRjpWikiにあったので,調査してみた.
基本的な使い方は
sweep(x, MARGIN, STATS, FUN="-", check.margin=TRUE, ...)となっていて,配列に対し,MARGINの方向にSTATSを用いてFUNを施す
という意味のようである.デフォルトでは引き算になるとのこと.
check.marginは次元のチェックをしてくれる.FALSEにするとわずかに速くなるが,
次元が一致していることをすでに知ってなければ使わないようにと書いてある.
基本はTRUEにしておけということらしい.
sweepというと一掃するいう意味で,試合で相手をシャットアウトしたときに
用いられるよね.語感と少し違うよう関数のような気がする.
それはさておき,実際に動作を試してみよう.
x <- matrix(1:16,nrow=4) y <- 1:nrow(x) sweep(x,1,y) sweep(x,2,y) sweep(x,1,y,"/")としてみると,実行結果は
> x [,1] [,2] [,3] [,4] [1,] 1 5 9 13 [2,] 2 6 10 14 [3,] 3 7 11 15 [4,] 4 8 12 16 > sweep(x,1,y) [,1] [,2] [,3] [,4] [1,] 0 4 8 12 [2,] 0 4 8 12 [3,] 0 4 8 12 [4,] 0 4 8 12 > sweep(x,2,y) [,1] [,2] [,3] [,4] [1,] 0 3 6 9 [2,] 1 4 7 10 [3,] 2 5 8 11 [4,] 3 6 9 12 > sweep(x,1,y,"/") [,1] [,2] [,3] [,4] [1,] 1 5.000000 9.000000 13 [2,] 1 3.000000 5.000000 7 [3,] 1 2.333333 3.666667 5 [4,] 1 2.000000 3.000000 4となった.
2番目の結果では行ごとに1から4が引かれていて,
3番目の結果では列ごとに1から4が引かれている.
MARGINで1を指定すると行方向,2を指定すると列方向に演算が
行われている.この辺はapply関数に良く似ていると思う.
また最後の例では,デフォルトの引き算から割り算に変えてみると,
行ごとにそれぞれ1から4で割られていることが分かる.
文章と実際の例があると分かりやすいですね.
2009年10月3日土曜日
aggregate関数の動作に関する調査
Rを用いてグループごとに集計したいという場合に用いるものに,aggregate関数があるらしい.今まではインデックスでグループごとに切り出して操作していた(フィルタという)が,それよりも楽に作業ができそうなので,どのような挙動になるか調べてみた.
基本的な使い方はヘルプによると,
aggregate(x, by, FUN, ...)
となっている.「データxをリスト構造のbyのグループごとに,関数FUNで統計量としてまとめる」という使い方のようである.FUNはスカラを返すものでないとダメらしい.早速
example(aggregate)
にあった例の一部を引っ張り出して
aggregate(state.x77, by = list(Region = state.region), FUN=mean) aggregate(state.x77, by = list(Region = state.region,Cold = state.x77[,"Frost"] > 130), FUN=mean)
で動作を確認してみる.アメリカの州ごとの国勢調査のデータから,各地域の平均値が得られるはずである.結果は,
> aggregate(state.x77, by = list(Region = state.region), FUN=mean) Region Population Income Illiteracy Life Exp Murder HS Grad Frost Area 1 Northeast 5495.111 4570.222 1.000000 71.26444 4.722222 53.96667 132.7778 18141.00 2 South 4208.125 4011.938 1.737500 69.70625 10.581250 44.34375 64.6250 54605.12 3 North Central 4803.000 4611.083 0.700000 71.76667 5.275000 54.51667 138.8333 62652.00 4 West 2915.308 4702.615 1.023077 71.23462 7.215385 62.00000 102.1538 134463.00 > aggregate(state.x77, by = list(Region = state.region,Cold = state.x77[,"Frost"] > 130), FUN=mean) Region Cold Population Income Illiteracy Life Exp Murder HS Grad Frost Area 1 Northeast FALSE 8802.8000 4780.400 1.1800000 71.12800 5.580000 52.06000 110.6000 21838.60 2 South FALSE 4208.1250 4011.938 1.7375000 69.70625 10.581250 44.34375 64.6250 54605.12 3 North Central FALSE 7233.8333 4633.333 0.7833333 70.95667 8.283333 53.36667 120.0000 56736.50 4 West FALSE 4582.5714 4550.143 1.2571429 71.70000 6.828571 60.11429 51.0000 91863.71 5 Northeast TRUE 1360.5000 4307.500 0.7750000 71.43500 3.650000 56.35000 160.5000 13519.00 6 North Central TRUE 2372.1667 4588.833 0.6166667 72.57667 2.266667 55.66667 157.6667 68567.50 7 West TRUE 970.1667 4880.500 0.7500000 70.69167 7.666667 64.20000 161.8333 184162.17
となった.リストの中に論理式が入った場合の例が興味深い.また,Southに分類される州でCold=TRUEとなるものはないということがすぐ分かる.
比較のため,面倒だがインデックス操作で挙動を確認してみる.
> apply(state.x77[state.region == "Northeast",],2,mean) Population Income Illiteracy Life Exp Murder HS Grad Frost Area 5495.111111 4570.222222 1.000000 71.264444 4.722222 53.966667 132.777778 18141.000000
結果の一行目に一致していることが確認できた.逐一インデックス操作で切り出していたらスクリプトを書くのも面倒なので,今後はaggregateを使おう.