const-refとd'torのvirtual dispatch
http://herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry
http://d.hatena.ne.jp/faith_and_brave/20080107/1199696826
Q3 : 参照がスコープから抜けるとき、どのデストラクタが呼ばれるでしょうか?
A3 : 一時オブジェクトのために呼ばれるのと同じデストラクタが呼ばれます。
当然の結果:Derived一時オブジェクトをconst Base&に持つことができます。
そしてそれは仮想ディスパッチなしのデストラクタが呼び出されます。
これは素晴らしいです。
…?
えーと、こういうこと?
#include <iostream> #include <string> using namespace std; //#define virtual /* */ struct base { string name; base(const string& n) : name(n) { cout << "base::base " << name << endl; } virtual ~base() { cout << "base::~base " << name << endl; } }; struct derived : public base { derived(const string& n) : base(n) { cout << "derived::derived " << name << endl; } virtual ~derived() { cout << "derived::~derived " << name << endl; } }; derived gen() { return derived("b"); } void foo() { const base& b = gen(); } int main() { derived a("a"); foo(); base* c = new derived("c"); delete c; }
ちゃんとvirtualをつけるとa, b, cのいずれもderivedとbaseの両方のデストラクタが呼ばれるけれど、virtualをつけない場合、cはbaseのデストラクタだけを呼ぶ(なぜならdeleteの時点ではcの型は静的にbase*に決まるから)。一方でbは、virtualの有無に関係なくderivedのデストラクタを呼ぶ。なぜならbに束縛された時点で、束縛した対象がderivedであることが分かっているから。
…へえ。
あとQ1の
[Jan 2 update: Note this only applies to stack-based references. It doesn't work for references that are members of objects.]
も重要…っと。
もひとつ
この件、もう少し考えてみて、何が起こっているのかやっと理解できたような気がします。
この話って要するに
- C#3.0は「型を明示しない静的型付け言語」になりました!
という派閥と、
という派閥の対立なんじゃないでしょうか。
前者から見れば「可能な限りvarを使って何を不自由するの?」ということになるし、後者から見れば「なんで型を書いちゃだめなの?」という話になる。
実際にはどちらも(特に前者)はそこまで過激な主張ではないんでしょうが、もしC#が今後この方向で進化していくなら、いつかそれが当たり前の日が来るかも知れません。ただ後者の立場の人たちに新しいプログラミングスタイルに欠陥や陥穽がないこと/回避する方法を納得させるまでは、しばらくは布教活動を続けることになるんでしょう。ま、最終的にはHeilsbergの腹一つなんでしょうが。
なんにしても当面C#を使う場合は、各開発プロジェクトごとに、上記どちらの視点でvarを捕らえるかをあらかじめ統一させるのが重要でしょうね*1。
で、まあ、私は(この記事のタグを見ても明らかに)C++のことしか考えていないので、ここからC++に話を捻じ曲げます。
C++の場合は明らかに後者ですよね。これはvarに相当する予約語がautoであるということでも(地味に)明らかなんじゃないかと思います。
C#の場合、varは「これから(型は知らんけど)変数書きますよ。名前は○○で、値は〜〜」と発想する書き方の中で使うわけですよね。だからvariable/variantのvar。
一方でC++のautoの場合は、「この変数○○の型は右辺から自動的に導出されますよ」と発想する書き方の中で使われる。だから「自動」のauto。
さらにC++は、conceptを導入することでより明確に「不特定多数の型を明確に宣言する方法」を強化する道を選びましたから、そのような明確化と逆行するautoの利用は、あくまでも補助的なものになるのではないかと思います。
てなことを
考えながら
http://blogs.wankuma.com/rti/archive/2007/12/27/114850.aspx
を読んでみると、
// (a) double amount = this.GetAmount(); this.SetAmount(amount); // (b) var amount = this.GetAmount(); this.SetAmount(amount); // (c) this.SetAmount(this.GetAmount());
は3つとも「書き手がそのコードを書いているときの意図」が違うよね、って話になるかと思います。
- (a)の場合、書き手はamountという「double型の変数を用意したい」のだし、
- (b)の場合、書き手はamountという「GetAmount()の戻り値を受ける変数を用意したい」のだし、
- (c)の場合、書き手は「GetAmound()の戻り値をSetAmount()に渡したい」のであって、
それ以上でも以下でもないはずです。
書き手がまったく異なる文脈で書いたコードを三つ並べて「(読み手から見て)意味は同じだ」と論じたところで、自動型推論に関する議論として導ける答えは
「これまでは上手く書くのが難しかった(b)が簡単に書けるようになったよ!」
という当たり前のことでしかないのではないかと。
http://d.hatena.ne.jp/faith_and_brave/20071225/1198582400
で言う「C++で受け入れられやすい」のはまさに(b)の意図のための構文がほしくてほしくてたまらなかったからなわけで。
誤解しないで頂きたいのは、ここで問題にしているのは「書き手の意図が分かるか否か」ではないということです。そんなの周りのコードを見れば分かることですからね。
なので
http://d.hatena.ne.jp/NyaRuRu/20071125/p1
の
「可読性」ってそんなに以心伝心できるもんですかね?
という部分はとても賛同できます。
#結論は違うけど
…てなことを書こうとしてたらεπιστημη師匠の
http://blogs.wankuma.com/episteme/archive/2007/12/27/114856.aspx
var children = new List<Child>();
僕はおそらく、このテの(省略目的の)varは使わない気がします。
childrenはList<Child>でなくてはならないとは思っていない、
Childの集合でありさえすればえぇのや、
ICollection<Child> children = new List<Child>();
なのよね僕のキモチ的には。
そうそう、それですよね。「僕のキモチ」=「書き手の意図」。
επιさんの例は動的な例ですが、C++使いとしては静的な例もなんぞ考えねばいかんぞいね…と思ったけど上手く書けないな(めっちゃ苦しいコードになる)。
auto Collection<Child>* children = new std::list<Child>();
とか書けるようになればいいんだけどなあ…。
#…以上、現実逃避モード終わり。しくしく。
型推論議論
じっくり腰を据えて考えてから書こうと思ってたんだけど、時間もないし、考えもまとまらないし、それでも自分なりに答えが出ちゃったっぽいので、てきとーに書いてみます。
なぜかこの話って「型を書かないと読みづらい」「いやそんなことない」って議論ばかり見てる気がするんですよね。そんなことより「型を書いた方が書きやすい」ようなコードはどんなコードか?を議論したほうが建設的な気がするんですが。
例えば、何かコードを書いていて、変数を作りたくなった、とします。
このときプログラマの頭の中には、その変数が「どういう意味を持つのか」がぼんやりとイメージできているはずです。
このイメージをコードに固着させる方法として、型のない(あるいは型宣言のない)言語では変数名を使うわけですよね。Rubyとか。だけど実はもう一つ、変数の型も、このイメージを手っ取り早く固着させる助けになってるんじゃないか……と、私は思ってるみたいです。
より正確には、たぶん、「この変数の型は○○です」ではなくて「この変数は××な性質があると思います」くらいの情報なんだと思うけど。
型による厳密な制約(「この変数は△△でなければいけません!」)はコンパイルが勝手に掛けてくれるから、そこまでは書く必要は無い。
一方で右辺のコンテキストに任せっきりにしてもよいほど投げやりでもない。
その中間に
「まあだいたいコイツ(変数)はこんな感じなわけっスよ。で、コイツを使って…」
みたいな思考過程があって、それをそのまま書き下せる方が簡単な気がするんだけどなあ…。
autoももうちょっと強力に、「右辺値の型にテンポラリな名前をつける」くらいの能力があるともっと楽しいかもなあ…とか、妄想してみたり。
auto VerticalIterator vitr = vertical_begin(table); auto HorizontalIterator hitr = horizontal_begin(table);
とか。もちろんこの型名を明示して別の変数が書けるとさらに楽しそう。
do {} while (0)
http://d.hatena.ne.jp/puruhime/20071118#1195318276
http://d.hatena.ne.jp/Isoparametric/20071113/1194905094
否定的な意見が多いみたいですけど、個人的には気に入っていて結構使うので、if(){}else{}にくらべて良いケースを。
あるコードで、複数の条件がすべて満たされないと実行しない処理があるとします。んで、その一つ一つの条件判定が数行ずつ掛かるとしましょう。
これが関数のトップレベルの場合、
result_type function(...) { // 条件Aの判定準備コード ... // 結果がcond_Aに入る if (! cond_A) return ERROR; // 条件Bの判定準備コード ... // 結果がcond_Bに入る if (! cond_B) return ERROR; ... // 条件Zの判定準備コード ... // 結果がcond_Zに入る if (! cond_Z) return ERROR; // ある処理の本体 ... }
と掛けますが、トップレベルでない場合、つまり、この「ある処理」の後に、ある処理の実行の有無と関係なく評価されるコードがある場合には、
{ // 条件Aの判定準備コード ... // 結果がcond_Aに入る if (cond_A) { // 条件Bの判定準備コード ... // 結果がcond_Bに入る if (cond_B) { ... // 条件Zの判定準備コード ... // 結果がcond_Zに入る if (cond_Z) { // ある処理の本体 ... } ... } } }
のようになって、ネストが深くなります。
一方、do {} while(0)なら
do { // 条件Aの判定準備コード ... // 結果がcond_Aに入る if (! cond_A) break; // 条件Bの判定準備コード ... // 結果がcond_Bに入る if (! cond_B) break; ... // 条件Zの判定準備コード ... // 結果がcond_Zに入る if (! cond_Z) break; // ある処理の本体 ... }
と書けます。
もちろん、gotoで脱出すれば良いんですけどね。
…いやなんか昔、gotoだとブロック脱出時にデストラクタが上手く動かないと信じていた時期があって、その影響なんですが。
…あれ?動きますよね、デストラクタ?gotoでも?
ん、autoって…
これと前後して最新ドラフトをぼーっと眺めていて初めて気がついたんだけど、storage-classとしてのautoってまだ残っているんですね。
auto x = 5; // OK: x has type int const auto *v = &x, u = 6; // OK: v has type const int*, u has type const int static auto y = 0.0; // OK: y has type double static auto int z; // error: auto and static conflict auto int r; // OK: r has type int
まあきっと完全に無視できる話だけど
auto c = 'a'; // int -> char ... c = 1024; // numeric_limits::max() > 1024 but numeric_limits ::max() < 1024
とかやってるプログラムがあると嵌るような。他のキーワード無かったんだろか。