blog

C++とポインタの将来

先週C++2013に参加して以来、C++についていろいろ考えてきましたが、そのうちのいくつかはポインタに関係しています。C++ 11ではポインタの更新はわずかでしたが、C++におけるポインタのセマンテ...

Jan 8, 2016 · 7 min. read
シェア

先週、C++ 2013に参加した後、C++についていろいろと考えましたが、そのうちのいくつかはポインタに関係しています。C++ 11では、ポインタにはわずかな更新しかありませんでしたが、C++におけるポインタのセマンティクスと使用法は、ここ数年で大きく変わりました。

まず、ポインタの本来の意味から説明すると、C++11のポインタはtype* pt = nullptr;という単純なものです。C++はポインタを再設計していないので、私の知る限り、Cもこの部分のセマンティクスを更新していません。しかし、C仕様ではポインタを定義し、CおよびC++でポインタを使用するための指示を与えています。実際、ポインタはメモリに格納された変数へのアドレスです。ポインタを再参照すれば、そのポインタが指す変数にアクセスできます。ポインタは実際には、それが指す値が有効かどうかを知らないベース変数であり、それが指す値が無効であるかどうかを感知することはできません。C言語では、0を指すポインタはどの値も指しておらず、したがって有効な値を持っていないことを意味します。それ以外のポインタはすべて、メモリ上の意味のあるアドレスを指しているはずです。しかし実際には、正しく初期化されていないポインタや、単にスコープ外のポインタもあります。

C++11 では、ポインタを 0 に適切に初期化する方法として、キーワード nullptr を使用します。 これにより、ポインタが現在 NULL であることをコンピュータに知らせることができます。ポインタを 0 に初期化するもう 1 つの一般的な方法は、0 を NULL などとして定義することですが、C++11 では nullptr を使用することで統一されています。 C++ では、変数のエイリアスのような参照も導入されています。参照は使用時に初期化する必要があるため、参照のライフサイクルの開始時に有効なアドレスを指す必要があるという利点があります。しかし、参照はポインタの再参照に過ぎないので、参照先の変数のスコープが終了すると、参照は無効になります。 ポインタを使うときはポインタを0に設定できますが、参照ではそれができません。

しかし、C++11とC++11標準では何かが変わりました。ポインタは言語のコア概念ですが、現代のC++コードやライブラリではほとんど見かけません。C++11 にさかのぼると、boost 社は、ポインタをカプセル化し、そのコア機構を演算子でオーバーロードする、一連の非常に便利なスマート・ポインタ・クラスを作成しました。スマート・ポインタはポインタそのものではなく、スタック上の変数またはオブジェクト・メンバです。スマート・ポインタはRAIIを使用して、ポインタの責任ではないポインタの問題のいくつかを解決します。バーテブラでメモリを割り当てるとき、new はその部分を指すアドレスを返すので、ダイナミック・メモリの一部を割り当てるたびに、オブジェクトを作成する操作ハンドルに相当するポインタを使用する必要があります。しかし、ポインタは単なる変数であり、変数の所有権を知りませんし、ヒープ上のメモリ空間を自動的に解放することもできません。スマート・ポインターはこの役割を担い、ポインターを所有し、変数がスコープ外に出たときにヒープ上の値を自動的に管理します。スタック上の値とは、例外が発生した場合でも、対応するスタックが破壊されれば、ヒープ上で管理されていた値が自動的に解放されることを意味します。

ここ数年、C++は、クラスとポインタの多用によるCから、I think WidgetやQTのようなオブジェクト指向フレームワークまで、さまざまなスタイルで使われてきました。ここ5~10年の間に発展した新しいスタイルは、モダンC++と考えられており、言語固有の拡張性を利用しようとする傾向や、アプリケーションごとに異なる機能を見つけようとする傾向があります。この傾向の中で、boostが****なC++フレームワークであったことは注目に値しますし、C++標準は標準ライブラリの設計においてこれを参考にしています。同時に、値セマンティクスが普及し、移動セマンティクスとともにC++の将来にとって重要なポイントとなりました。Meeting C++のTony van Eerds氏のメモスライド、ポインタについて考えさせられました。参照セマンティクスと値セマンティクスの2つのカラムがあり、そのキャッチーな件名は次のとおりです:

そのため、C++11 やそれに続く C++14 では、値のセマンティクスを使用する傾向がポインタの使用に影を落としていました。ポインタはまだフェッチバックグラウンドで機能しますが、新しい C++14 では new も delete も直接使用できなくなり、new は make_shared/make_unique に抽象化されます。 shared_ptrもunique_ptrも値セマンティック型として動作します。また、スマートポインタはdeleteを使ってスコープの最後でメモリを解放します。このことから、C++のポインタはすべて異なる「役割」を果たすことができるのか、あるいは置き換えることができるのかを考えました。

継承と仮想関数

ポインタの非常に重要な使い方は継承で、同じインターフェイスを共有する一連の型の値を指すのに使われます。Shapeという基底クラスがあり、areaという仮想関数を持ち、Rectangle、Cirecle、Triangleという派生クラスがあります。また、Rectangle、Cirecle、Triangleと呼ばれる派生クラスもいくつかあります。さて、異なる形状のオブジェクトへのポインタを保持するポインタコンテナがあり、それぞれが独自の面積計算方法を持ちます。C++では、特にオブジェクト指向の場合、このようなポインタの使い方が一般的です。また、Boostのポインタ・コンテナには、スマート・ポインタの要素が空になると自動的に解放されるものもあります。

仮想関数の呼び出しは通常少し遅く、コンパイラが最適化するのも簡単ではありません。そこで、もし実行時にその型がわかっていれば、実行時に仮想関数のポインタを使う代わりに、静的分散やコンパイラのポリモーフィズムを使って、対応する仮想関数のメソッドを正しく呼び出すことができます。これはCRTPと呼ばれるパターンとして実装されています。 最近の研究により、gcc4.8では、これによりパフォーマンスが向上することが示されています。興味深いことに、典型的なgcc4.9では、オプティマイザが動的分散をさらに最適化することができます。あるいは、ポインタに戻りましょう。

ふていポインタ

ポインタは、引数としてオプションの値の範囲を取る関数や、通常はデフォルトで0となる不確定な値を返す関数で使用されることがあります。また、returnの場合、関数は実行に失敗したことを示すNULLポインタを返します。同様に、ポインタに対するハンドルの役割を果たすスマート・ポインタも使用できます。しかし、これはしばしば使いすぎにつながったり、未定義の役割に取って代わらなかったりします。このため、代わりにオプショナル値型を使用する必要があります。オプショナル値型は、格納する値が有効かどうかを判断するために使用されます。Boostライブラリには、オプショナル値型を表すboost::optionalがあります。したがって、C++14 でも同様のオプショナル型の導入を検討してください。そのため、現時点では std::optional は技術プレビューに移行され、将来的に C++14 または C++1y の一部になる予定です。

例えばstd::set::insertはpair<iterator,bool>型を返し、その第2引数は要求された値がセットコンテナに挿入されるかどうかを示します。コンテナは通常、無効であることを示すために末尾のイテレータを返しますが、値を返すように要求された場合、この役割は以前はポインタによって表され、ポインタが0であれば関数が失敗したことを意味しました:

optional<MyValue> ov = queryValue(42); 
if(ov) 
  cout << *ov; 
else 
  cerr << "value could not be retrieved"; 

このように、オプショナル型とスマート・ポインタ型はポインタのセマンティクスの一部を置き換え、その役割を果たします。しかし、これらは値セマンティクスであり、主にスタック上で使用されます。

有効なポインタ

C++のポインタ使用法に関する私の考えを書いたとき、私は主にポインタが他のもので置き換えられるシナリオに焦点を当てましたが、実際にはポインタがまだ有用なシナリオがあるという事実を過小評価していました。reddit、メール、ソーシャルメディアからのフィードバックに感謝します。

shard_ptrには対応するweak_ptrがありますが、unique_ptrには対応する相手がありません。ここでは、所有者でない生のポインタの使用が必要です。例えば、親オブジェクトと子オブジェクトからなるツリーやグラフの場合です。しかし、C++では将来、その代わりにexempt_ptrが追加される予定です。

ポインターは、関数内で渡された値を扱うときにはまだ有用です。Herb Sutterが5月に「これ」についてのGotWという非常に良い記事を書いています。セマンティクスは、関数内でどのように値を渡したり返したりすべきかに影響します。

この表はエリック・ニーブラースのメモからのものです。

Eric Nieblerは、できる限りmoveセマンティクスを使うように言っています。オプションのパラメータの例として、vector::emplace_backは要素を適切な位置に移動するだけなのにパラメータを取ります。いくつかの出力パラメータは値を返すので、コンパイラは移動セマンティクスや CopyEllision 最適化技術を使用することができます。オブジェクトを入出力として受け取るいくつかの入出力パラメータでは、非参照もオプションで最適化されますが、Ericはノートの中で、オブジェクト・アルゴリズムの状態をコンストラクタのスロット・パラメータとして使用すべきだと指摘しています。

ポインタも定数参照を渡すときに同じことができますが、ポインタがNULLかどうかをテストする必要があるという点で、多少異なります。個人的には、関数/メソッドやコンストラクタを渡すときには、ポインタよりも参照を渡す方が好きです。

ポインタ計算

さようなら、ポインター?

理論的には、C++はポインタなしでも動作しますが、ポインタはC/C++言語の中核概念であるため、ポインタ自体は存在し続けます。しかし、ポインタの役割は変わり、C++を使用する際にポインタについて考える必要はなくなります。C++ が進化を続ける中、C++11 と C++14 はより抽象的で開発者に優しい方向に進んでいます。スマートポインタとオプショナル型を使用して、ポインタは安全な値型に適したカプセル化されるか、完全に置き換えられました。

Read next

モバイル・クラウド時代のCRMとはどのようなものだろうか?

企業管理ソフトウェアの革新は遅れています。近年では、インターネット、特にモバイルインターネットは、ITのコンシューマライゼーションの潮流の到着を促進し、インターネットベースまたはモバイルインターネットアプリケーションの多くは、Dropbox、EvernoteとGoogleの関連クラウドストレージ製品などのエンタープライズ市場に、それらの伝統的なエンタープライズソフトウェア企業は、より大きな歴史的な手荷物のために、それは簡単に好転することは困難です。また、市場全体に新たな発展の機会をもたらすインターネットとモバイルインターネットを受け入れます。

Jan 6, 2016 · 2 min read