D初心者向け、パート2

こんにちは、Habr!



前の記事のテーマを継続します 。 ここでは、@ safe、@ trusted、pure、nothrow、OOPに関するいくつかのポイントなどの概念について説明します。



すべてのコードはデフォルトで@システムです(いくつかの例外を除きます)。つまり、低レベルの操作(ポインターの操作)を実行できます。 このモードでは、DでC / C ++とまったく同じ操作をすべて実行できます。 これには、C / C ++で発生する可能性のある多くのメモリエラーも含まれます。 いくつかの制限を守れば、多数のエラーを回避する方法があります。 実際、これはD言語のサブセットであり、SafeDと呼ばれ、それを操作するロジックにより、JavaとC#に似ています。 このモードは@ safe属性で有効になり、未定義の動作を引き起こす可能性のあるコード内のすべての操作を禁止します。

SafeDの制限:



@ trusted属性を使用すると、safe内でシステムコードを使用できます。 しかし、そのような機会は細心の注意を払って扱われるべきです-すべての信頼できる機能をより徹底的にチェックしてください。



@nogc属性は、ガベージコレクターを使用した操作や@nogc以外の関数の呼び出しを禁止しています 。ガベージコレクターの無効化の詳細については、 こちらを参照してください



pure属性は、関数がグローバルまたは静的な可変変数を使用しないことを示します。 これにより、コンパイラーはいくつかの最適化を使用できます。 このルールには1つの例外があります-デバッグブロック:

void func() pure { debug writeln( "print from pure" ); //      ... }
      
      





デバッグモードでコンパイルするには、dmd -debugフラグを指定する必要があります(簡単ですが、まだ)。

この属性は、クラスや構造を記述するときにも非常に役立ちます(これについては後で説明します)。



nothrow属性は、関数がExceptionから継承された例外をスローしないことを保証します。 これは、彼女がエラー例外をスローすることを防ぎません。 開発者によって設計されたとおり、エラー例外は回復不能であるため、それらをキャッチする意味がありません。 また、これは、彼女がtry-catchブロックに囲まれている場合、nothrow以外の関数を呼び出すことを妨げません。



すべての関数リテラルとテンプレート関数には、可能であれば@ safe、@ nogc、pure、およびnothrow属性があります。 このような機能による各属性の自動割り当てには、対応する条件が満たされている必要があります。



@無効にすると、関数呼び出しが禁止されます。 これは、デフォルト関数を持たない構造を作成する場合に便利です。例えば:

 struct Foo { @​disable this(); //    @​disable this(this); //  ,     this( int v ){} //        } void main() { Foo a; //    ,     auto b = Foo(3); //     auto c = b; //   ,     }
      
      





これは組み込み関数だけでなく使用できます:

 interface A { int bar(); } class B : A { int bar() { return 3; } } class C : B { @disable override int bar(); } void main() { auto bb = new B; bb.bar(); auto cc = new C; cc.bar(); //    }
      
      





しかし、このアプローチの使用はお勧めしません。Cから継承されたクラスの関数をオーバーライドすることを禁止しませんが、その呼び出しは実行時に失敗します。 この動作には例外メカニズムがあります。



非推奨の属性は、APIをスムーズに変更するときに役立つ警告を表示します。これにより、ユーザーはどこでもそのような関数の呼び出しを削除できます。 この属性は、コンパイルされるメッセージとして文字列を取ります。

 deprecated("because it's old") void oldFunc() {}
      
      





属性は別の方法でコードに適用できます。「属性のみ」は属性の属性に続く宣言に適用され、属性の後に中括弧が使用されると、上位レベルブロックと内部クラスに適用されます(関数内で、属性を持つブロックの宣言は禁止され、意味がありません)この場合、コロンを使用して、ファイルの最後に適用されます。

 module test; @​safe: ... //       pure { int somefunc1() {} //  @​safe,  pure int somefunc2() nothrow {} // @​safe, pure  nothrow }
      
      







整理されたシンプルなもの。 今、最も不明瞭なトピックを明らかにする価値があります:構造とクラスと不変で共有されます。



簡単な例を見てみましょう:独自のデータ構造を使用して、あるスレッドから別のスレッドへメッセージキューを整理したいとします。

構造から始めましょう。 タイムスタンプとメッセージが必要で、構造は常に不変であるとします(他のオプションは必要ありません)。

 import std.stdio; import std.traits; import std.datetime; //  "  ",   ,     template isMessage(T) { enum isMessage = is( Unqual!T == _Message ); } struct _Message { ulong ts; immutable(void[]) data; //   @disable this(); //        immutable: //    immutable ,      immutable this(T)( auto ref const T val ) { static if( isMessage!T ) { //      ts = val.ts; data = val.data; } else { //     data       static if( isArray!T ) data = val.idup; else static if( is( typeof(val.array) ) ) //  ,   range data = val.array.idup; else static if( !hasUnsharedAliasing!T ) //    ,   ,      data = [val].idup; else static assert(0, "unsupported type" ); //     ts = Clock.currAppTick().length; } } //   auto as(T)() @property { static if( isArray!T ) return cast(T)(data.dup); else static if( !hasUnsharedAliasing!T ) return (cast(T[])(data.dup))[0]; else static assert(0, "unsupported type" ); } } alias Message = immutable _Message; //        ,    // ///   -  .          unittest { auto a = Message( "hello" ); auto b = Message( a ); assert( a.ts == b.ts ); assert( b.as!string == "hello" ); auto c = Message( b.data ); assert( a.ts != c.ts ); assert( c.as!string == "hello" ); auto d = a; auto e = Message( 3.14 ); assert( e.as!double == 3.14 ); }
      
      





「なぜ不変なのですか?」と尋ねることができますか? ここで質問はあいまいです。 マルチスレッドプログラミングで可変メッセージが本当に必要ですか(さらに深刻なタイプについてはもう少し)。 メッセージは、「1回限り」の小さなメッセージです。 たとえば、Rustでは、すべてのデフォルト変数は不変です。 これにより、同期による不要なhemoがさらに回避され、エラーとコードが少なくなります。 しかし、まだ必要な場合。 まず、コンストラクターは純粋でなければなりません-これにより、1つのコンストラクターを使用して任意のタイプのオブジェクトを作成できます(この例では、時間がかかる関数を使用しますが、クリーンではありません)。 次に、アクセス方法のコードをオブジェクトに部分的に複製する必要があります。 コンストラクターをクリーンにできない場合は、コードを複製する必要があり、そのアプリケーションのケースを明確に示します。 例:

 struct CrdMessage { ulong code; float x, y; this( ulong code, float x, float y ) pure //   { this.code = code; this.x = x; this.y = y; } this( in CrdMessage msg ) pure //   { code = msg.code; x = msg.x; y = msg.y; } float sum() const @property { return x+y; } //  float sum() shared const @property { return x+y; } //  float sum() immutable @property { return x+y; } //  }
      
      





重複はmixinテンプレートを使用して削除できますが、それだけではありません。 このような構造の共有オブジェクトを使用しない場合は、メソッドのconstバリアントでのみ使用できます(不変オブジェクトが呼び出します)。 オブジェクトを共有できると明示的に述べているため、共有メソッドが必要です。したがって、 同期の責任を負います。 これは、例コードにエラーが含まれていることを意味します。別のスレッドで値が変更される可能性があることを考慮していません。 不変オブジェクトはスレッド間で共有でき、型システムは呼び出すメソッド(constまたはshared const)を選択できないため、constおよびshared constメソッドは不変オブジェクトのメソッドを呼び出すのに十分ではありません。 また、constメソッドは不変と異なる場合があります。constの場合はオブジェクトへの参照が変更されないことを保証し、不変の場合は構造体のすべてのフィールドがその寿命を通じて一定のままであることを保証するため、constメソッドでいくつかのアクションを実行する必要がある場合があるためです不変である必要はありません(追加コピーなど)。 このような型システムにより、共有コードを記述する際に実行するアクションを検討し、注意を払うことができますが、最初は痛みを引き起こす可能性があります。

むかしむかし私自身この痛みを経験しましたが、私は頑固で文盲でした、私はすぐにすべてを望みました
その結果、私はいくつかの注意を払って望みどおりに動作するコードを取得しましたが、それ以降は原則としてあまり変更されていません: ここに型指定されていないデータを格納する構造、 ここにストレージ構造を使用してストリーム間で送信されるメッセージがありますデータ。



マルチスレッドアプリケーションの作成に戻りましょう。 キューを最も単純なキューに実装します(メモリ割り当ての最適化については考えないでください)。

 synchronized class MsgQueue { Message[] data; //    //  ,       foreach bool empty() { return data.length == 0; } Message front() { return data[0]; } void popFront() { data = data[1..$]; } void put( Message msg ) { data ~= msg; } }
      
      





はい、それはとても簡単です! 実際、構造に関係するすべてのものを(共有、不変などの点で)クラスに適用できます。 synchronizedキーワードは、クラスが共有されることを意味しますが、synchronizedはクラスでのみ使用でき、共有とは重要な違いがあります。 次の順序で:

 class MsgQueue { Message[] data; import core.sync.mutex; Mutex mutex; //   this() shared { mutex = cast(shared Mutex)new Mutex; } // - Mutex   shared  ... void popFront() shared // { synchronized(mutex) //  ,   { data = data[1..$]; } } ... }
      
      





各メソッドを共有属性でマークすることはできませんが、クラス全体を共有することができます。 MsgQueueクラス(およびその他)のオブジェクトを同期オブジェクトとして使用することもできます。

 shared class MsgQueue { Message[] data; ... void popFront() { synchronized(this) { data = data[1..$]; } } ... }
      
      





各オブジェクトは、Object.Monitorインターフェイスを実装する基本クラス(Object)から同期オブジェクト(__monitor)を取得するため、同期オブジェクトになります(Mutexも実装します)。



メソッド内のブロックではなく、メソッド全体を同期する場合、クラス自体のインスタンスを同期オブジェクトとして使用する場合は、同期メソッド全体を作成できます。

 shared class MsgQueue { Message[] data; ... void popFront() synchronized { data = data[1..$]; } ... }
      
      





クラスのすべてのメソッドをスレッドセーフにする必要がある場合は、共有などの同期をクラスレベルに取得してから、元のスペルに戻ります。



いくつかの明白でない点を明確にできたことを願っています。 繰り返しますが、何かに特別な注意を払う必要があると思う場合は、それについて書いてください。 私はここで、私には自明でないと思われるものだけを持ってきました。



メッセージ付きのプログラムの全文
 import std.stdio; import std.traits; import std.datetime; //  "  ",   ,     template isMessage(T) { enum isMessage = is( Unqual!T == _Message ); } struct _Message { ulong ts; immutable(void[]) data; //   @disable this(); //        immutable: //    immutable ,      immutable this(T)( auto ref const T val ) { static if( isMessage!T ) { //      ts = val.ts; data = val.data; } else { //     data       static if( isArray!T ) data = val.idup; else static if( is( typeof(val.array) ) ) //  ,   range data = val.array.idup; else static if( !hasUnsharedAliasing!T ) //    ,   ,      data = [val].idup; else static assert(0, "unsupported type" ); //     ts = Clock.currAppTick().length; } } //   auto as(T)() @property { static if( isArray!T ) return cast(T)(data.dup); else static if( !hasUnsharedAliasing!T ) return (cast(T[])(data.dup))[0]; else static assert(0, "unsupported type" ); } } alias Message = immutable _Message; synchronized class MsgQueue { Message[] data; bool empty() { return data.length == 0; } Message front() { return data[0]; } void popFront() { data = data[1..$]; } void put( Message msg ) { data ~= msg; } } unittest { auto mq = new shared MsgQueue; mq.put( Message( "hello" ) ); mq.put( Message( "habr" ) ); string[] msgs; foreach( msg; mq ) msgs ~= msg.as!string; assert( msgs == ["hello", "habr"] ); } void randomsleep(uint min=1,ulong max=100) { import core.thread; import std.random; Thread.sleep( dur!"msecs"(uniform(min,max)) ); } import std.string : format; void sender( shared MsgQueue mq, string name ) { scope(exit) writefln( "sender %s finish", name ); foreach( i; 0 .. 15 ) { mq.put( Message( format( "message #%d from [%s]", i, name ) ) ); randomsleep; } } void receiver( shared MsgQueue mq ) { uint empty_mq = 0; bool start_receive = false; scope(exit) writeln( "reciver finish" ); m: while(true) { if( mq.empty ) empty_mq++; if( empty_mq > 10 && start_receive ) return; foreach( msg; mq ) { writefln( "[%012d]: %s", msg.ts, msg.as!string ); randomsleep; start_receive = true; } } } import std.concurrency; void main() { auto mq = new shared MsgQueue; spawn( &receiver, mq ); foreach( i; 0 .. 10 ) spawn( &sender, mq, format( "%d", i ) ); writeln( "main finish" ); }
      
      









また、標準ライブラリDには、「グリーン」ストリーム(これは念のためです)の実装、 オフサイトのドキュメントがあります。



All Articles