東方算程譚

Oriental Code Talk ── επιστημηが与太をこく、弾幕とは無縁のシロモノ。

目次

Blog 利用状況

ニュース

著作とお薦めの品々は

著作とお薦めの品々は
東方熱帯林へ。

あわせて読みたい

わんくま

  1. 東京勉強会#2
    C++/CLI カクテル・レシピ
  2. 東京勉強会#3
    template vs. generics
  3. 大阪勉強会#6
    C++むかしばなし
  4. 東京勉強会#7
    C++むかしばなし
  5. 東京勉強会#8
    STL/CLRによるGeneric Programming
  6. TechEd 2007 @YOKOHAMA
    C++・C++/CLI・C# 適材適所
  7. 東京勉強会#14
    Making of BOF
  8. 東京勉強会#15
    状態遷移
  9. 名古屋勉強会#2
    WinUnit - お気楽お手軽UnitTest

CodeZine

  1. Cで実現する「ぷちオブジェクト指向」
  2. CUnitによるテスト駆動開発
  3. SQLiteで組み込みDB体験(2007年版)
  4. C++/CLIによるCライブラリの.NET化
  5. C# 1.1からC# 3.0まで~言語仕様の進化
  6. BoostでC++0xのライブラリ「TR1」を先取りしよう (1)
  7. BoostでC++0xのライブラリ「TR1」を先取りしよう (2)
  8. BoostでC++0xのライブラリ「TR1」を先取りしよう (3)
  9. BoostでC++0xのライブラリ「TR1」を先取りしよう (4)
  10. BoostでC++0xのライブラリ「TR1」を先取りしよう (5)
  11. C/C++に対応した、もうひとつのUnitTestFramework ─ WinUnit
  12. SQLiteで"おこづかいちょう"
  13. STL/CLRツアーガイド
  14. マージ・ソート : 巨大データのソート法
  15. ヒープソートのアルゴリズム
  16. C++0xの新機能「ラムダ式」を次期Visual Studioでいち早く試す
  17. .NETでマンデルブロ集合を描く
  18. .NETでマンデルブロ集合を描く(後日談)
  19. C++/CLI : とある文字列の相互変換(コンバージョン)
  20. インテルTBBによる選択ソートの高速化
  21. インテルTBB3.0 によるパイプライン処理
  22. Visual C++ 2010に追加されたSTLアルゴリズム
  23. Visual C++ 2010に追加されたSTLコンテナ「forward_list」
  24. shared_ptrによるObserverパターンの実装
  25. .NETでマンデルブロ集合を描く(番外編) ── OpenCLで超並列コンピューティング
  26. StateパターンでCSVを読む
  27. 状態遷移表からStateパターンを自動生成する
  28. 「ソートも、サーチも、あるんだよ」~標準C++ライブラリにみるアルゴリズムの面白さ
  29. インテルTBBの同期メカニズム
  30. なぜsetを使っちゃいけないの?
  31. WPFアプリケーションで腕試し ~C++でもWPFアプリを
  32. C++11 : スレッド・ライブラリひとめぐり
  33. Google製のC++ Unit Test Framework「Google Test」を使ってみる
  34. メールでデータベースを更新するココロミ
  35. Visitorパターンで遊んでみたよ
  36. Collection 2題:「WPFにバインドできる辞書」と「重複を許す検索set」
  37. Visual C++ 2012:stateless-lambdaとSQLiteのぷち拡張
  38. 「Visual C++ Compiler November 2012 CTP」で追加された6つの新機能

@IT

  1. Vista時代のVisual C++の流儀(前編)Vista到来。既存C/C++資産の.NET化を始めよう!
  2. Vista時代のVisual C++の流儀(中編)MFCから.NETへの実践的移行計画
  3. Vista時代のVisual C++の流儀(後編) STL/CLRによるDocument/Viewアーキテクチャ
  4. C++開発者のための単体テスト入門 第1回 C++開発者の皆さん。テスト、ちゃんとしていますか?
  5. C++開発者のための単体テスト入門 第2回 C++アプリケーションの効率的なテスト手法(CppUnit編)
  6. C++開発者のための単体テスト入門 第3回 C++アプリケーションの効率的なテスト手法(NUnit編)

AWARDS


Microsoft MVP
for Visual Developer - Visual C++


Wankuma MVP
for いぢわる C++


Nyantora MVP
for こくまろ中国茶

Xbox

Links

記事カテゴリ

書庫

日記カテゴリ

思想の違い

---- C# ----
public class Base {
  public Base() { initialize(); } // 初期化メソッドを呼ぶ
  public virtual void initialize()
    { System.Console.WriteLine("Base::initialize"); }
}

public class Derived  : Base {
  public override void initialize() // 初期化メソッドを再定義
    { System.Console.WriteLine("Derived::initialize"); }
}

public class Program {
  public static void Main() {
    Base b = new Derived();
  }
}
---- 実行結果 ----
Derived::initialize

Derivedをnewしたんだから初期化メソッドは当然Derived::initializedだよね、と。
# JavaもC#と同じ挙動を示します。

ところがC++は違います。
---- C++ ----
#include <iostream>

class Base {
public:
  Base() { initialize(); }
  virtual void initialize()
    { std::cout << "Base::initialize\n"; }
};

class Derived : public Base {
public:
  virtual void initialize()
    { std::cout << "Derived::initialize\n"; }
};

int main() {
  Base* b = new Derived();
  delete b;
}
---- 実行結果 ----
Base::initialize

Derivedのコンストラクトに先立って基底クラスであるBaseのコンストラクトが行われます。
Baseのコンストラクタ内で呼ばれたinitializeについて
"BaseのコンストラクトなんだからBaseのメソッドが呼ばれるのがスジ"
と考え、Base::initializeが動きます。
基底クラスのコンストラクトを導出クラスに撹乱されてなるものかってーぽりしぃですわね。

どちらが正しいてのはないのでしょうが、僕はC++の堅実さが好ましく感じられます。
が、一方で初期化メソッドを再定義したいって思いも強くあります。

…あなたはどちらがお好みですか?

投稿日時 : 2007年9月21日 11:33

コメントを追加

# re: 思想の違い 2007/09/21 12:10 ながせ

DerivedをnewするのだからDerivedを呼んでくれたほうが、なじめますね。

# re: 思想の違い 2007/09/21 12:23 オノデラ

C# の「override」を「virtual」にすれば C++ と同じように Base が呼ばれますね。

# re: 思想の違い 2007/09/21 12:31 IIJIMAS

中身がDerivedなのでDerived::initializeと思いますが、わかりやすい、すなおなのはBase::initializeが呼ばれる方…と思うので、どちらが好みなのか迷ってしまいます…

# re: 思想の違い 2007/09/21 12:40 囚人

以前、似たような事書きました。
http://blogs.wankuma.com/shuujin/archive/2006/04/18/22541.aspx

MSDN のどこに書いてあったか忘れてしまいましたが、C# でも、コンストラクタで仮想関数(メソッド)を呼ばないように、とあった記憶があります。

C++ でも C# でも Java でも、この手の挙動は「仕様として明言」されていないんじゃないかなぁと思うんですが、どないなんでしょ?

# re: 思想の違い 2007/09/21 13:00 IIJIMAS

囚人さん
>MSDN のどこに書いてあったか忘れてしまいましたが、C# でも、コンストラクタで仮想関数(メソッド)を呼ばないように、とあった記憶があります。

ここですね
コンストラクタのデザイン
http://msdn2.microsoft.com/ja-jp/library/ms229060(VS.80).aspx

派生クラスのコンストラクタより先にメソッドが呼ばてしまうからってことですね。

# re: 思想の違い 2007/09/21 13:02 επιστημη

> C# の「override」を「virtual」にすれば C++ と同じように Base が呼ばれますね。

うそーん
基底クラスにあるのと同じシグニチャのvirtualメソッドを
導出クラスに定義するときゃ override もしくは new が
必須ちゃいますのん?

# re: 思想の違い 2007/09/21 13:04 επιστημη

> 派生クラスのコンストラクタより先にメソッドが呼ばてしまうからってことですね。

んむ。C++屋の思考では"仮想関数テーブル差し替えのタイミング"てことなんですが。
C#ではコンストラクト前、C++ではコンストラクト後
ってゆー違いなんだな。

# re: 思想の違い 2007/09/21 13:08 IIJIMAS

>導出クラスに定義するときゃ override もしくは new が
必須ちゃいますのん?

省略するとnewを付けた時と同じように解釈されて、継承してない別のメソッドになるんですよね。
コンパイルエラーはでなくて”overrideかnewをつけて”ってワーニングがでます。

# re: 思想の違い 2007/09/21 13:17 επιστημη

あーホントだ warning だー。

warning CS0114:
 'Derived.initialize()' は継承されたメンバ
 'Base.initialize()を隠します。...

ってことは、C#では書きようによってコントロールできるわけね。

# re: 思想の違い 2007/09/21 13:27 Imabeppu

私は、Derived:: initialize が動く方がしっくり来ます。"virtual なんだから、継承先のメソッドが呼ばれるのがスジ" と考えてしまいます。そのほうが素直な気がしますが、C++ がそうしなかったのには議論があったんでしょうね。

基底クラスのコンストラクトを導出クラスに撹乱されたくなければ virtual にしなければよいことですし、派生クラスで再定義したいものを virtual にしていると思うんですけどね。初期化処理の一部をテンプレートパターン的に派生クラスに任せたいときは、別の初期化関数を作ってそれを呼ばせないといけないんですね。

# re: 思想の違い 2007/09/21 13:54 ゆーち

あちきも以前から、この件はよろしくないと主張してきています。

virtual size_t GetSize(){
return sizeof(*this)
};
をコンストラクタで利用できないのは、本当に困ります。

επιστημη さん、なんとかしてください。

# re: 思想の違い 2007/09/21 14:08 まつあに

もちろん、C++の思想のほうが一貫性があって好ましいです。コンストラクトされていないオブジェクトのメソッドを呼べちゃヘンだもの。

初期化メソッドの問題、ありますよね。特にテンプレート・メソッド・パターン的な初期化メソッドを用意して、基底クラスのコンストラクタで呼び出したいなんて茶飯事。いつも困ってしまいます。


# re: 思想の違い 2007/09/21 14:12 囚人

>私は、Derived:: initialize が動く方がしっくり来ます。"
>あちきも以前から、この件はよろしくないと主張してきています。


100無理だと思うし、C++ でも C# でもやってはいけないと思いますよ。

仮想テーブル云々の話もありますが、呼び出す仮想関数がインスタンスメンバを使っていないという保証はあるでしょうか?

1.派生クラスのコンストラクタは、必ず基底クラスのコンストラクタが実行された後でなければならない。
2.仮想関数はインスタンスメンバを使うかもしれない。

という事で、基底クラスで呼び出した仮想関数として派生クラスの定義は呼べないし、呼んだら動作は不定だと思いますよ。
なので、C++ の挙動が正しいし、C# はぶっちゃけ間違ってます。

# re: 思想の違い 2007/09/21 14:35 επιστημη

そこだよな。

基底クラスのコンストラクト時に(初期化できてない)導出クラスのメソッドを呼べちゃイカンだろ、と。
そのメソッドがメンバ変数をアクセスしてないなら許しても構わんだろうか...妖しい匂いぷんぷんですけど ^^;

# re: 思想の違い 2007/09/21 14:35 ゆーち

なるほどー。
コンストラクタで何をする?ってのが明確でなけりゃいけませんね。
・クラス用のメモリを確保する
・初期値を設定する
・仮想テーブルを構築する
・基底クラスコンストラクタ実行
・導出クラスコンストラクタ実行
という流れかな?
確かにインスタンスメンバにアクセスしちゃいかんですね。
Javaはどーなってるんでしょ?ってのが気になり出しました。

それを仕様と解釈できれば・・・
ま、切ない話になりますね(w

ちなみに Delphi も導出側呼べます。
Borland C++ Builder では、導出側を呼べるテクニックが用意されてます。
移植性皆無(^◇^;ですが、利用させていただいております。

# re: 思想の違い 2007/09/21 15:02 Imabeppu

うーむ、なるほど……。
C# や Java はどうなってるんでしょう?
メンバ変数にアクセスできちゃうんでしょうか?
そのとき、中身は null なのでしょうか?
int などには 0 が入っているのでしょうか?
そこで値をいじれるんでしょうか?
その後初期化されるんでしょうか? いや、それは変ですね。
うーむ……。

# re: 思想の違い 2007/09/21 16:14 シャノン

俺はC#派だなぁ。

派生クラスを new したときに呼ばれる基底クラスのコンストラクタの実行中は、「派生クラスのコンストラクト中」と考える。
要するに、自クラスの初期化中ということ。
「コンストラクト中のインスタンスのメンバ変数が参照できてはならない」と一般化すると、仮想関数に限らない問題になる。

要するに、初期化されていない変数をいじってしまう可能性は、非仮想関数の中だってありえるじゃないの、と。
それはコンストラクタから呼ばれうるという実装を知っているから、未初期化変数をいじらないように気をつけて作るからいいんだと言うならば、仮想関数の場合にも「この関数はコンストラクタ中から呼ばれ得るので気をつけろ」って書いとけば済む話じゃないの、です。

ついでに、基底コンストラクタは派生クラスのコンストラクタの一部だと考える根拠。
プログラミングでは、複数の派生クラスを持つ基底クラスがある時、まずその共通部分を初期化してから、各々の派生クラス特有の部分を初期化するという流れを確かにとる。
けど、現実世界になぞらえると、そうはならない。
例えば車クラスからバスとトラックが派生しているとき、バスでもトラックでもない「車」をまず完成させてから、それをバスやトラックに加工するという流れは踏まない。
それ以前に、車クラスは抽象的なものだから、それ自体を new できない。

というわけで、C#派。

# re: 思想の違い 2007/09/21 16:42 NyaRuRu

> んむ。C++屋の思考では"仮想関数テーブル差し替えのタイミング"てことなんですが。
>C#ではコンストラクト前、C++ではコンストラクト後

んーと,.NETの仮想関数をC++の (差し替えの起こりうる) 仮想関数テーブルで理解・説明しようとするのはミスリーディングな気がします.

とりあえずそのあたりは気をつけた方がよいかと.CLRの実装は,C++流の仮想関数テーブルといくつも相違点があります.

# re: 思想の違い 2007/09/21 16:46 επιστημη

> .NETの仮想関数をC++の (差し替えの起こりうる) 仮想関数テーブルで理解・説明しようとするのはミスリーディングな気がします.

御意。仮想関数呼ばれるたびにReflection使って同じシグニチャのメソッド引き当ててもいいんだしね。

# re: 思想の違い 2007/09/21 16:48 επιστημη

> 仮想関数の場合にも「この関数はコンストラクタ中から呼ばれ得るので気をつけろ」って書いとけば済む話じゃないの、です。

うーん...導出クラスのメソッドでは何するかわからんのだから気ぃつけようがないし、危険な振舞を抑止できひんわけでしょぉ?

導出クラスのメソッドが呼ばれることがないって保証されてるなら、自分の中で思う存分気ぃつければいいけどさ。

# re: 思想の違い 2007/09/21 17:05 NyaRuRu

まあ .NET の場合,コンストラクタ中で this.GetType() (これ自体は非仮想メソッド!) が安全に使えるってのはそれなりにありがたい話な気がします.
んで,コンストラクタ中で自分の真の型が分かるのだから,中途半端に隠しても仕方がないでしょうし.


せっかくなので C# ならではなコードでも.
public abstract class Base
{
  public Action<int> Foo;
  public Base()
  {
    Foo = foo;
  }
  protected abstract void foo(int i);
}

# re: 思想の違い 2007/09/22 2:09 なちゃ

そもそもこんなことにどっちが正しいとか間違ってるとか言うのがおかしいと思いますが。

virtualメソッドが状況によって呼ばれ方が変わるなんて「一貫性がある」とは思えないと感じる人もいるだろうし、
単にどこを基準、あるいは重点としてみているかで変わるだけでしょう。

本当に一貫性とか言うなら、そもそもコンストラクタからvirtualなメソッドは呼べないのが正しい気がします。
呼べる時点でどこかで何らかの一貫性が無くなるということなんですから。

個人的にはC#の動きの方が直感的だと思いますけどね。
私の直感では、ですが。

# re: 思想の違い 2007/09/25 22:28 Jitta

これを思い出した
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=40767&forum=7

# ZHDhhLolVSo 2011/12/22 22:14 http://www.discreetpharmacist.com/fre/index.asp

Totally agree with you, about a week ago wrote about the same in my blog..!

# pxNXkWagQEXk 2012/01/07 13:58 http://www.luckyvitamin.com/p-30033-garden-of-life

Comrade kill yourself.

タイトル
名前
URL
コメント