- PR -

Managed C++ != C++

投稿者投稿内容
ya
大ベテラン
会議室デビュー日: 2002/05/03
投稿数: 212
投稿日時: 2002-07-23 12:36
すいません、引用部分、間違ってしまいました。

それとちょっと追加の書き込みを。
引用:

いいえ。それはありえません。それから、GCとマルチスレッディングは無関係です。GCを実行しているスレッドと自分のスレッドとでレースコンディションは発生しません。


ありえないのは賛成です。
最低限DBAccessのインスタンスが、OleDbConnectionの参照を保持しているのですからGCによって解放される事はないはずですね。
GCに関しては、マルチスレッドは無関係だという意見も同意です。ただしファイナライザに関してはマルチスレッドを考えなくてはいけないと思います。デストラクタの問題はそちらのことのような気もしますが。
NothingButXMLInfoSet
大ベテラン
会議室デビュー日: 2002/07/16
投稿数: 116
投稿日時: 2002-07-23 12:43
引用:

yaさんの書き込み (2002-07-23 12:36) より:
ただしファイナライザに関してはマルチスレッドを考えなくてはいけないと思います。デストラクタの問題はそちらのことのような気もしますが。


ファイナライザで考慮しなければならない事柄が、TLSと[ThreadStatic]以外はどうしても思い浮かびません。

TLSなどを使っているのであれば、その時点で自らマルチスレッド地獄に足を踏みいれているのですから、このスレの文脈で言われている「考慮したくないのに考慮しなくてはならない場面」にはあたらないと思っています。

その他に考慮する必要があるとお考えなのはどういう場面でしょうか?私もマルチスレッドに詳しいわけではありませんので、この機会にぜひ勉強したいと思います。
ya
大ベテラン
会議室デビュー日: 2002/05/03
投稿数: 212
投稿日時: 2002-07-23 22:22
引用:

ファイナライザで考慮しなければならない事柄が、TLSと[ThreadStatic]以外はどうしても思い浮かびません。


これは前に出てきたSoundクラスでも既に出ているような気もします。
C++では、コンストラクタでリストに登録し、デストラクタでリストから削除します。この単純な作業(マルチスレッドを考慮する必要がなければリスト操作の排他処理が不要)で、今実行中のプログラム内のそのインスタンスのすべてを把握する事が出来ます。これは、クラスで内部的に管理しているわけですから、独立性を高める事ができるいい方法です。

そして、話をC#に持っていきますが、これをそのまま実現しようとして、コンストラクタでリストに登録し、ファイナライザでリストから削除しようとします。C++で使っていた技術そのままですね。すると、ファイナライザの実行スレッドが決まっていないため、リスト操作において、マルチスレッドを考えて実装しなくてはなりません。

この解決策として、WeakReferenceを使えば、解放されたものはわかるのだからそのままリストに残してしまえばいい、という方法がありました。ただしこれはあくまで、リスト操作のタイミングをずらしてマルチスレッドの問題を起こらなくする手法だと思います。確かにこの方法で問題ないと思いますが、一種の「別スレッドでファイナライザが実行される事への解決策」だと思います。

ただ、この場合そもそもファイナライザを使うのが間違いです。ファイナライザの実行タイミングはいつ解放されるかわからないGCに依存します。これではPlayAllでどの音が鳴るのか管理できません。よって、Disposeパターンを使って、IDisposableインターフェイスを実装します。これは、C++で言うところのデストラクタの処理をこのメソッドに持たせるわけです。つまり、サウンドリソース(おそらくアンマネージリソース)と、リストからの削除を。

ここで問題になるのは、Disposeメソッドは、ファイナライザでも呼び出すべきだということです。実際に.NETのクラスライブラリも呼び出していたと思います。これがないと、もし解放を忘れた場合にアンマネージリソースはいつまでたっても解放されません。結局ファイナライザからリストの要素の削除が行われる事があるわけです。するとファイナライザは別スレッドで実行される可能性があるので、マルチスレッド対策が必要です。その対策としては、先ほどと同じように破棄のタイミングをずらせばよいです。PlayAllにIsDisposedのチェックを入れればよいだけですし。あるいはそれよりも、きちんとリスト操作でlockをかけたほうが単純かも知れませんが。

結局、後始末においてリスト操作が必要なものがあるクラスはすべて考えないといけないってことになるでしょう。

ちなみに、マルチスレッドを考慮しなくても良くなるのは無理のような気もします。コレクション系が、列挙とかも考慮すると完全にスレッドセーフにするのは難しいような気がするからです。ただ、C#はその辺とても綺麗になっているので、別段怖がらずにマルチスレッドを使ってもよいと思いますが。

結局なぜGC中立派かと言えば、例えば、C++で、参照カウント方式の簡易スマートポインタの実装はそれほど苦ではありません。また、auto_ptrで十分な場合も多いですし。しかも、それを利用したとしても、コンストラクタでリストに登録し、デストラクタでリストから削除する方法が使えます。必要なくなれば即時デストラクタをそのスレッドで呼び出し&解放されますから、先ほどのC++のSoundの実装みたいにこのクラスをシングルスレッドのみで使う場合はマルチスレッドについて考えなくてもよいですし。

メモリコンポジションや、参照カウント方式で解決できない参照等が問題にならない場合、.NETのGC方式よりもこちらのほうが優れているような気もします。というか、Disposeとファイナライザの2種類後始末が存在していることよりも単純です。
もちろん、.NETのGCが悪い、といっているわけではないです。様々な状況を考慮すれば、この実装になるのは納得がいきます。ただし、すべて.NET方式GC最高!というのはいいすぎではないのかと。C++でスマートポインタ程度の簡単なもので十分なものも多いわけですし、それならば、このような問題(それに限らず、いつ、どのスレッドで実行されるかまったくわからないことから生じる問題)も起こりません。今まで生成破棄管理を使い分けていた方からすれば、強引に統一している感は否めないような気がします。


以上、長文すいません。
NothingButXMLInfoSet
大ベテラン
会議室デビュー日: 2002/07/16
投稿数: 116
投稿日時: 2002-07-23 23:26
すいません、yaさんが以前に示されたC++コードをC#化すると、意味合いとしては以下のような感じになるということでいいでしょうか?

コード:

class A {
internal static System.Collections.ArrayList ar = new System.Collections.ArrayList();
static int num =0;
int state;
internal A() {
state = num++;
ar.Add(this);
}

~A() {
ar.Remove(this);
System.Console.WriteLine("*{0}", this.state);
}

static void Enumerate() {
foreach (A a in ar)
System.Console.WriteLine(a.state);
}

static void Main() {
A[] aa = new A[10];
for(int i = 0; i < 10; i++)
aa[i] = new A();
foreach (A a in aa)
System.Console.WriteLine(a.state);
aa = null;
Enumerate();
}
}


仮にOKだったとして、ここでaa=null;とEnumerate();の間に次の2行を挿入すると、どうなるかというと、

コード:

aa = null;
System.GC.Collect(System.GC.MaxGeneration);
System.GC.WaitForPendingFinalizers();
Enumerate();


Enumerateが普通に実行されまして、最後に*付きの数字(~Aで表示)が表示されます。つまり、(C#に関する限り)ArrayListがthisを持っている限り、そのthisに対して~A()は呼び出されないということです。~A()を呼び出すには、次のコードを外部から実行しなければなりません。

コード:

A.ar.RemoveAt(index);


このコードを実行しない限りFinalizeは呼ばれないのですから、Finalizeの中で ar.Remove(this); を実行する理由はまったくありません。

もちろんAppDomainを定義してその中で型を利用し、使い終わったらAppDomainをUnLoadすればstaticも消えますのでFinalizeが呼ばれます。その場合も ar.Remove(this); を実行する理由はまったくありません。

いずれにせよ、staticなArrayListにオブジェクトを格納した場合、そのArrayList(またはその中身)を解放しない限り、各オブジェクトのFinalizeは呼び出されません。そして、Finalizeが呼び出されたということは、少なくともそのthisに関しては誰も参照していない状態になったわけですから、自分に関するリスト操作をする必要などないのです。

というわけで、やはりFinalizeとマルチスレッディングのスキルは直結しないという結論に達します。いかがでしょうか?

[ メッセージ編集済み 編集者: NothingBut.NETFX 編集日時 2002-07-24 02:23 ]
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2002-07-24 08:25
こんにちは。
すみません、朝一しか見ていないので遅れます。

引用:

NothingBut.NETFXさんの書き込み (2002-07-23 12:11) より:
引用:

Jittaさんの書き込み (2002-07-23 09:02) より:
「デストラクタは別のスレッドで実行される」ことを思い出しました。ということは、DBAccessのインスタンスより、それが持っているOleDbConnectionのインスタンスが先に解放されてしまっている(可能性がある)ということですか?!


いいえ。それはありえません。それから、GCとマルチスレッディングは無関係です。GCを実行しているスレッドと自分のスレッドとでレースコンディションは発生しません。


そうであって欲しいのですが、動きを見ている限り、意図しないときに解放されているのです。
仕様がどうこうというのではなく、現実にそうなっているのです。
C++標準仕様を見直しましたが、デストラクタは「delete演算子やスコープからはずれることによってメモリから解放される場合に呼び出される」んですよね。ということは、デストラクタ中では、インスタンスおよびインスタンスが保持しているメモリは有効なわけです、よね。しかし、Managed C++では、デストラクタ中でメンバ変数の処理を行おうとしたら、メンバ変
数のインスタンスがない、という例外が投げられるのです。
OleDbReaderのCloseメソッドでOleDbConnectionがクローズおよび解放されるのでしたら、それは別の問題ですけど。


引用:

引用:

開発者が意図しないうちに解放されるおそれがあるということは、解放しておいてほしいのに解放してくれない、ということもあり得ますね!?


「解放しておいてほしい」ということを表現する手段がありませんので、そのようなこともありえません(Finalize/Disposeではマネージヒープは解放できない)。


そうです。だから問題/おかしい/どうして?と感じているのです。
いらなくなったときに、「これ、いらないよ」と言えないというのは、不安です。
本当に解放されるのか、いらないこと/使わなくなったことが伝わっているのか。
たとえば、VB6の頃には、明示的にNothingを参照させないといつまでもメモリを保持していることがありました(と、元同僚にきき、yaさんが強参照が云々とポストされている)。そういうことが.NETのガーベッジコレクションでも発生しないのでしょうか。
オンラインマニュアルからは、その辺が読みとれませんでした。

引用:

引用:

(...(略)Finalizeってprotectedメンバなのに、外から呼べてしまえるのはコンパイラのバグじゃないですか???)


Finalizeは外からは呼べません。Protectedメンバーですから。MC++でdeleteを発行すると、cl.exeはそれを__dtorというメソッドへの呼び出しとしてCILを出力します。__dtorメソッドの可視性は、「デストラクタ」の可視性に依存します。このメソッドの本体は自動作成され、内部でFinalizeメソッドを呼び出しています。delete pa;という呼び出しはFinalizeを呼んでいるわけではありません。


[/quote]
public __gc class A {
private:
String *a;
public:
A(void){
a = new String("");
};
~A(void){
delete a; // これはString::delete()がないのでエラー
};
};
というクラスを作りました。「delete a;」がエラーになったので「a->Finalize();」とやると、通るんです。String::Finalize()はProtectedメンバーですよね。それならば、class Aからは呼べないはずですよね?

#GCなので解放はしなくてもいいことは、後でわかりました。
#Stringクラスが確保しているメモリの内容を変更する手段がない、ことも、後でわかりました。
#.NETの勉強期間をおかずに実戦投入なので、手探りです。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2002-07-24 08:57
こんにちは。
yaさんのポストや.NETオンラインマニュアルを見ていて、なんだかC++でやるよりもC#でやった方が、言語系の再勉強という中断期間も含めて、短期間で実装できるんじゃないか、という気がしてきました。

複数人であたっているのですが、1ソリューションに複数プロジェクトを入れています。VBのプロジェクトからVC++のプロジェクトは参照できるのですが、VC++のプロジェクトからはVC++のプロジェクトがリンク時エラーになる!考えてみれば当たり前です。DLL化されているのですから、DLLを呼ぶコードを入れなければいけない。しかし、VBでは「参照」に登録すれば使えるのだから、どうせならVC++でも同じ作りにしておいて欲しかった。

[quote]
yaさんの書き込み (2002-07-23 12:13) より:
引用:

 また、デストラクタが勝手にFinalizeに変更されますが、これの実装方法が?です。と
いうのも、クラス宣言の中で実装すると、
class A {
public:
~A(){};
};
で実装できるのに、cppファイルに記述する時は
void Finalize(void){}
でないとリンクでエラーになる「ことがある」のです。これは開発環境も絡んできます
けど。小一時間悩みました。
[/qoute]
デストラクタの定義部に戻り値(void)を書いているからではないのですか?あるいは引数にもvoidを書いているか。もし書いていないのなら、勘違いです。申し訳ありません。



__nogcで作って、リンクまで通したものを、宣言を__gcに変更したのです。
引数部には、「何もない」ことを明確にするため、今まで(Borland C++を使っていました)ずっと入れていて、問題はありませんでした。
コード:
class A {
public:
    ~A(void);
};

A::~A(void)
{
    // Borland C++ Builder 1~5 は、これでもOK
    // 「C++ Standard Class」 を指定したウイザードで作ったスケルトンも、こうなってますね
}



ところが、別のプロジェクトでは、Finalizeではなく、~Aで通っているのです!!同じように、ウイザードで作ったスケルトンに肉付けしたのに・・・


----以下、愚痴
プロジェクトメンバーのうち、数人はハードウェアの設計部隊からリストラで回されてきた人で、C++言語はおろか、プログラム設計のノウハウもありません。一応、VC6でC言語を習ったというので、それならば私が慣れているBorland C++ Builderより、VS.NETの方が取っ付きやすいだろうと思ってVS.NETを使うことにしたのですが、これならBCBの方が安くあがったかと、後悔しています。
ya
大ベテラン
会議室デビュー日: 2002/05/03
投稿数: 212
投稿日時: 2002-07-25 19:27
引用:

すいません、yaさんが以前に示されたC++コードをC#化すると、意味合いとしては以下のような感じになるということでいいでしょうか?


微妙です。おそらくリストからの削除は、Disposeメソッドに実装するのが良いと思いますが、それを呼ばなければプログラムが実行されている限り「クラス外部に」参照が存在しなくても永遠に破棄されません。これは、サウンドリソースというある程度大きくなる事が予想されるものを保持している以上、少し問題です。
簡単に言えば、つくって使い捨て、という利用法をこの参照保持が閉ざす事になります。アンマネージリソースクラスは、使い終わったら必ずDisposeメソッドを呼び出すことが推奨されますが、クラスライブラリでもファイナライザにDisposeを行うコードが入っているように強制はいけないのでしょう。結局、ここでしない奴が悪い、で片付けてしまえば話はこれで終わりです。

ただ、もう少し続けるので付き合ってください。
Disposeにおいては、前におっしゃっていたように、機能的に見劣りする部分があります。参照カウント管理でもできればいいのですが、Proxyパターンを使って、スマートポインタを自分でうまく実装する方法がすぐには思いつきません。というか、GCがあるのにそんなものが必要なのかも微妙な気がします。
結局この解決策として、WeakReferenceを使う事を思いつきました。本来保持しているとカウントしてほしくないクラス内部の参照だからです。で、普通にファイナライザにDisposeを書く。すると先ほどいったようにDispose内部でリストの削除を行っています。もしこのタイミングでPlayAllを呼ばれると例外はいちゃうなと。この時点でマルチスレッド問題が…うんぬんを思いました。
もちろん、こんな事するやつはまずいないっていうのはわかるんですが(何がなるかもわからないのにPlayAll呼び出すやつがいるとは思えない)、それでも何か気持ち悪いので、対策を考えます。
まずその1。普通にlist操作するときにlockする。
その2。WeakReferenceなんだし、DisposeもIsDisposedぐらいは実装するんだから、破棄されたものはわかる。リストにはそのまま残しても問題ないだろう。問題なら、適当なタイミングでクリアすればいいし。
この問題に対する対策なので、「マルチスレッド問題」と。

結局、たぶんこれは、C#のプログラミングスタイルからすると異質なものなんでしょう。だから論点がずれたような。飛躍してすいませんが、この問題は根本的には、デストラクタがない、ということに集約されると思います。そのことをクラスの設計段階から考えた上でプログラムすれば、この問題にはほとんどあたりそうにないですね。
ただ、リスト操作をファイナライザで行う事はないっていうのは私はどうも断言できないんで、ファイナライザの動作を頭の隅に置いておきます。少し冗談が入っていますが、このようなものとか。
class Foo
{
~Foo(){ Log.Add(ToString() + " is Finalized"); }
}


引用:

yaさんのポストや.NETオンラインマニュアルを見ていて、なんだかC++でやるよりもC#でやった方が、言語系の再勉強という中断期間も含めて、短期間で実装できるんじゃないか、という気がしてきました。


C#の方がはるかに覚えるのも簡単だろうと思うので、C#使ったほうが良いかと。Managed C++は正直、難易度が高すぎだと思います。特にはじめて触ってどうこうできるレベルではもはやなくなっているような…。

引用:

複数人であたっているのですが、1ソリューションに複数プロジェクトを入れています。VBのプロジェクトからVC++のプロジェクトは参照できるのですが、VC++のプロジェクトからはVC++のプロジェクトがリンク時エラーになる!考えてみれば当たり前です。DLL化されているのですから、DLLを呼ぶコードを入れなければいけない。しかし、VBでは「参照」に登録すれば使えるのだから、どうせならVC++でも同じ作りにしておいて欲しかった。


C++ではヘッダがなければ、つまり事前宣言がなければ使用できないので、コードをさかのぼっていけば絶対にどれを参照しているかわかります。絶対にそのソース内で書いていないものは使えない。そうすると、色々と問題があるような気がします。
参考になるかわかりませんが、私は、1ソリューション(以下これが実行ファイル一つのとき)に複数プロジェクトを入れていますが、まず、ソリューションのフォルダにbinフォルダを作って、そこにすべて実行ファイルを生成するようにしています(その下にReleaseとDebugは作りますが)。そして、C++のプロジェクトには#using参照の解決で、そのフォルダを追加するようにして(Debugならば..¥..¥bin¥Debugとか)、依存関係や、ビルド順序をいじるようにしてます。これで、
#using "foo.dll"
とでも書いておけば、リンクできますし、デバッグもそのまま出来ます。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2002-07-30 18:42
遅レス失礼

引用:

yaさんの書き込み (2002-07-25 19:27) より:
C++ではヘッダがなければ、つまり事前宣言がなければ使用できないので、コードをさかのぼっていけば絶対にどれを参照しているかわかります。絶対にそのソース内で書いていないものは使えない。そうすると、色々と問題があるような気がします。
参考になるかわかりませんが、私は、1ソリューション(以下これが実行ファイル一つのとき)に複数プロジェクトを入れていますが、まず、ソリューションのフォルダにbinフォルダを作って、そこにすべて実行ファイルを生成するようにしています(その下にReleaseとDebugは作りますが)。そして、C++のプロジェクトには#using参照の解決で、そのフォルダを追加するようにして(Debugならば..¥..¥bin¥Debugとか)、依存関係や、ビルド順序をいじるようにしてます。これで、
#using "foo.dll"
とでも書いておけば、リンクできますし、デバッグもそのまま出来ます。



 なるほど、そういう方法がありましたか(._.) φ メモメモ
 includeはしています。ところが、クラス宣言で実装している関数以外が「実装がない」という意味のリンクエラーになったのです。VC++.NETの本をいくつかそろえていましたが、例示されているすべてのソースコードが.h一本!なので、不思議に思っていました。
 なるほど、usingって、そのように使うのですね・・・・

 ところが、ちゃぶ台返しが発生しました。VB.NET1本でいこうとか、Webアプリにしようとか…
金と直結した仕事じゃないからなぁ。あ〜あ、3ヶ月はどこに行ったんだろう・・・・・・・

スキルアップ/キャリアアップ(JOB@IT)