20190324の新人プログラマ応援に関する記事は3件です。

[翻訳]プログラミングはソフトウェアエンジニアリングとは違う

※この記事はSamer Buna氏の「Software Engineering is different from Programming」を翻訳したものです。Twitterより翻訳の許可を頂きました。ありがとうございます。

プログラミングはソフトウェアエンジニアリングとは違う

全てのソフトウェアエンジニアはプログラミングができますが、全てのプログラマはソフトウェアをエンジニアリングできません。

エンジニアリングの比喩として、ソフトウェアエンジニアという用語を好まない人もいます。この記事はその用語については言及しません。気に入らなければ、Software Author、Software Craftsperson、またはSoftware Artistに置き換えてください。

ソフトウェアエンジニアは、職業として高品質のソフトウェアを書く人を意味します。彼らは科学と統計を用い、そして単なるお金を稼ぐ仕事だと見なさない人です、プログラミングの方法を知っていても、ソフトウェアエンジニアとはなりません。

誰でもプログラムを学ぶことができます。それは簡単です。誰でも自分自身のマシンで動くプログラムを作ることができますが、それらが他の人のマシンで動作することは保証しません。

これに似た私のお気に入りとしては、誰もがシャワーの中で歌って自分を楽しませることができますが、それがパーティータイムになると、あなたは自分自身の歌の録音を再生しません。あなたはプロの音楽を楽しみます。

もっと似たものを?もちろんです。

  • 私たちは学校で数学と執筆を学びましたが、それによって数学者や著者になりませんでした。
  • 私たちのほとんどは簡単に調理することを学ぶことができますが、それが多くの人々を養う時が来たとき、私たちはシェフを雇います。
  • あなたは一から家を建てるために近所の便利屋を呼びません。

この記事で私が共有したい主なメッセージは、単純なプログラムは設計されたプログラムとは大きく異なるということです。

プログラミングという行為は、その最も単純な定義では、何らかの出力を生成するために何らかの入力でコンピューターに指示を与えることです。

ソフトウェアエンジニアリングは、多くのユーザーのために問題を解決することを目的としたコンピュータプログラムの設計、作成、テスト、および保守のことです。それは堅牢で安全な解決策を創り出すことであり、それらは長持ちし、根本的な問題の周りにあるいくつかの未知の問題も解決します。

ソフトウェアエンジニアは、彼らが解決する問題、彼らが提供するソリューション、それらのソリューションの限界、彼らのプライバシーへの影響、そしてセキュリティへの影響についてすべてを理解しています。

誰かが問題そのものを理解していないならば、彼らはそれに対する解決策をプログラムしようとはしません。

解決の考え方

ソフトウェアエンジニアは、自分のキャリアを単なるプログラムを書くこととは考えていません。彼らはニーズを満たし、問題を解決するという観点から考えています。これは重要なことで、なぜならば全ての問題がプログラムを必要とするわけではないからです。いくつかの問題は、既存のプログラムやまとめられた複数のプログラムによって解決することができます。いくつかの問題は早期に行動することによって完全に防ぐことができます。良いプログラムの設計というものは、将来の問題を防ぐための計画もよく含まれています。

「知識人は問題を解決し、天才は問題を起こらないようにする。」
アルバート・アインシュタイン

通常、複雑な問題は複数のプログラムを書く必要があります。いくつかの問題は並行して実行するプログラムを必要とし、他の問題はプログラムを順次実行することを必要とします。いくつかの問題はユーザーを教育することによって解決することができます。

プログラムを書く前に、ソフトウェアエンジニアは質問をします。

  • どのような問題を解決しようとしているか?
  • それらを解決するためにコードを書くこと以外に何ができるか?
  • これらの問題をコードで解決しやすくするために何ができるか?

コードの品質

優れたプログラムは明確で読みやすく、簡単に拡張することができ、他のプログラムとうまく機能します。それらをメンテナンスすることは悪夢ではありません。コードの品質は天秤にかけられるものではありません。締め切りや感情のためにずさんなショートカットを使用することは決して受け入れられません。

エンジニアリングソフトウェアの最も重要な側面の1つは、拡張性を考慮してゼロから設計することです。ソフトウェアを変更することは避けられない事実です。ユーザーはより多くの機能とソフトウェアを使用するためのより簡単な方法を要求するでしょう。

ソフトウェアは通常、それ自体ではあまり役に立ちません。有用なソフトウェア機能は、複数のソフトウェアが互いに通信し、データを交換し、そしてユーザーにデータとインターフェースを提示するタスクに関して共同作業をするときに始まります。

それを念頭に置いてプログラムを設計する必要があります。彼らはどんなメッセージを受け入れますか?どのようなイベントが監視されていますか?どのようなメッセージが出されますか?通信を認証および認可する方法はどうしますか?

優れたプログラムのもう1つの重要な側面は、コードの明確さです。テストの数やテストカバレッジレポートの数ではありません。このコードが他の人に読まれるかどうかという簡単な質問をします。あるいは、今日のコードの作成者である私は、これから数週間でこのコードを理解できるかどうかです。

「コンピュータサイエンスには、キャッシュの無効化と命名という 2つの難しいことしかありません。」
フィル・カールトン

コードの読みやすさは、あなたが思う以上に重要です。残念ながら、コードを明確にするための適切な指標はありません。良いソフトウェアパターンと慣習を記憶することは助けになるかもしれませんが、しばしば十分ではありません。優れたソフトウェアエンジニアは経験と直感をもってコードの明快さに目を向けます。次の執筆についての比喩は完璧です。大量の単語のリストを知っているだけでは、簡潔で明確な内容を書くのに役立ちません。

「短い手紙を書く時間がなかったので、代わりに長い手紙を書いた。」
マーク・トウェイン

プログラムはうまくいかないでしょう。それらを修正するときにそれらを簡単に修正できることは、良いソフトウェアの重要な性質です。プログラムで発生したエラーは明確なメッセージを持ち、監視のためにどこかに集中的に記録されるべきです。新しいエラーが報告されたら、それを修正する必要がある人はそのエラーをデバッグできるはずです。それらはシステムにフックし、実行コンテキストに関する情報をいつでも読むことができるはずです。彼らは、システムのあらゆる部分についての期待を容易に確認できるはずです。

環境とテスト

ソフトウェアエンジニアがプログラムを書くとき、彼らは彼らのプログラムが多くの異なる環境で、異なるリソースのマシンで、そして異なるタイムゾーンで動作することを確認します。ソフトウェアは、さまざまな画面サイズと向きで動作する必要があります。また、限られたメモリやCPUの使用を余儀なくされていることに対処する必要があります。

たとえば、Webブラウザ用のソフトウェアを作成するときは、さまざまな主要ブラウザすべてで動作する必要があります。デスクトップソフトウェアを作成するとき、それはほとんどの場合MacとWindowsユーザーのために働く必要があります。データに依存するアプリケーションを作成するときには、そのデータを取得するための接続が遅くなったり、しばらくの間完全にオフになったりする場合に備えて、ソフトウェアが機能する必要があります。

ソフトウェアの一部を書くために、ソフトウェアエンジニアは彼らが想像できるすべての可能なシナリオを考えようとします、そして、彼らはこれらのシナリオをテストすることを計画します。これは、予期しないことが起こらないハッピーパスと呼ぶものから始まりますが、さらに重要なことは、起こりそうなすべての問題を文書化し、そのためのテストを計画することです。ソフトウェアエンジニアの中には、これらのシナリオをシミュレートするコードをテストケースと呼ぶことから始めます。その後、これらのテストケースすべてに合格する目的のコードを作成します。

ソフトウェアエンジニアは、通常あいまいで不完全なソフトウェア要件を理解しています。優秀なソフトウェアエンジニアのユニークなスキルは、ソリューションの書き方ではなく、ソリューションに何を入れるべきかを特定することです。

コストと効率

ほとんどの場合、ソフトウェアエンジニアは問題を迅速に解決できます。あなたが経験豊富なプログラマーを雇うことがより高いコストを意味すると思うならば、もう一度考えてください。あなたが雇うプログラマーの経験が豊富であればあるほど、彼らはより早く、堅牢で正確、信頼でき、そして保守可能な解決策を提供することができます。これは長期的に見て全体的にコストが低いことを意味します。

プログラムを実行するコストも考慮する必要があります。すべてのプログラムはコンピュータリソースを使用し、それらは解放されません。ソフトウェアエンジニアは、コンピュータリソースを不必要に使用しない効率的なプログラムを書くでしょう。たとえば、頻繁に使用されるデータのキャッシュはここで適用される戦略の1つですが、プログラムをより高速かつ効率的にすることができるのは、数千のツールやバリエーションの1つにすぎません。

初心者のプログラマーはあなたに安い解決策を与えるかもしれませんが、その解決策を実行することはあなたが経験豊富なプログラマーがそもそも効率的な解決策を作成した場合よりはるかにあなたとあなたのクライアントに多大な費用をかけます。

使いやすさ

優れたプログラムは、ユーザーエクスペリエンス(UX)を念頭に置いて設計されています。ヒューマンコンピュータインタラクションは、数え切れないほどの調査研究と発見を伴う大きなトピックです。これらの調査結果が広く受け入れられるほど、ソフトウェアは優れたものになります。

この大きな領域の味を知るために、ここでいくつか例を挙げましょう。

  • ユーザーが自分の電子メールアドレスなどのデータを入力すると予想される入力フォームを設計するとき、優れたプログラムは電子メールアドレスに使用される大文字と小文字を無視します。それはまたそれのまわりの余分なスペースを削除します。CAPSLOCKキーがオンになっているため、ユーザーに苦労しないでください。電子メールは小文字で一意です。プログラムが新しいEメールアドレスを受け入れている場合は、そのことを早めに検証して、おそらく間違ったアドレスを使用したという明確なメッセージをユーザーに伝えます。これには、@記号を付けないなどの明白な検証の問題が含まれますが、スペルミスのある "gmail.ocm"を使用するような明白でない検証の問題も含める必要があります。」
  • ユーザに何かをするようにリダイレクトするとき、良いプログラムは彼らの元の位置を覚えていて、彼らが終わったときにその位置にそれらをリダイレクトするでしょう。優れたプログラムはまた、ユーザーに要求される将来のステップに関連付ける必要がある、定義済みのデータと対話をすべて記憶しています。たとえば、Expedia(訳注:ホテル・航空券等の予約サイトのこと)でゲストとしてフライトを検索していたとします。その後、アカウントを作成することにしました。以前の検索はすべて新しいアカウントに保存され、まったく別のマシンからもそれらにアクセスすることができます。
  • 良いプログラムは、ユーザーのシナリオを念頭に置いて設計されています。ユーザーの立場に身を置いてください。機能を追加しないでください。先日、私はfrequent flyer number(訳注:VIP会員番号のようなもの)を含めるのを忘れてユナイテッドのフライトを予約しました。確認を得た後、私は会員番号FF#をフライトに追加するためにユナイテッドのウェブサイトに行きました、そしてそれを理解するのに10分もかかりました。サイトに明確な道はなかったので、その機能につながる可能性のあるすべてのリンクを調査する必要がありました。この機能が利用可能なページにアクセスしたところ、大きな形で埋められていたため、初めて見ることができませんでした。旅行者情報を編集し、そのフォーム上の約20の入力要素をスクロールし、使用したいFF#の種類を選択し、さらにフォーム全体を送信するために必要な電話番号を入力する必要があることがわかりました。

信頼性、セキュリティ、そして安全性

これらはおそらく、アマチュアからソフトウェア専門家を引き離す最も重要な点です。彼らは安全でセキュアな解決策を書く責任があることを知っています。

ソフトウェアは、悪い入力、悪い状態、および悪いインタラクションに対して回復力がなければなりません。これは達成するのが非常に難しいです、そしてそれは我々がソフトウェアのミスで消えていく人々についての物語を聞く主な理由です。

ユーザーは悪いまたは間違った入力でソフトウェアを使用しようとしています。意図的にソフトウェアを壊し、そのソフトウェアに代表されるリソースに侵入しようとする人もいます。最近のEquifax(訳注:米国の信用情報会社)の大惨事の原因とされているとされる人物は、仕事をしていないと非難されています。その仕事とは、一般に公開されているすべてのソフトウェアで悪意のある入力に対する回復力を高めるためのものです。

セキュリティの話は、悪意のある悪意のある入力だけでなく、通常の入力についても同様です。ユーザーが自分のパスワードを忘れた場合、何回試行を許可できますか?あなたはそれらを後で締め出しますか?他の誰かがそれらをロックアウトしようとしている場合はどうなりますか?ユーザーが暗号化されていない接続で自分のパスワードを送信することを許可しますか?アカウントへのログイン試行が異常な場所から行われた場合はどうなりますか?ログインが自動化されていると思われる場合はどうしますか?

クロスサイトスクリプティングや偽造要求、中間者攻撃、単純なソーシャルフィッシングからユーザを保護するために何をしますか?サーバーにDDoS攻撃を受けた場合、バックアップ戦略はありますか?これらの質問は、計画されるべき多くの懸念のうちのいくつかを挙げるためだけのものです。

安全なプログラムは、機密情報を平文としてではなく、分割が非常に困難なアルゴリズムによる一方向暗号化データとして格納します。これは、プログラムとデータが危険にさらされた場合のバックアップ戦略です。ハッカーは暗号化されたデータを見つけるでしょうが、それはほとんど役に立ちません。

ソフトウェアは悪い状態になり、修正する必要があります。予期しない問題がプログラムのせいで発生します。あなたがそれに気付いておらず、あなたがそのことを計画していないのなら、あなたはソフトウェア専門家ではなく、単なる危険なプログラムの作者です。

ソフトウェアの欠陥は見えません。既知の欠陥を予測して防止するための私達の知的能力は限られています。ソフトウェアエンジニアが、正しい安全なソフトウェアを作成するのに役立つ優れたツールの価値を理解しているのはこのためです。

ツールを受け入れる

より良いツールが必要なのは間違いありません。ツールは大きな違いを生み、しばしば過小評価されています。

FTPでファイルをデプロイする必要があることを想像してみてください。Chrome DevToolsを使わずにネットワークとパフォーマンスの問題をデバッグすることを想像してみてください。ESLintとPrettierなしでJavaScriptを書くのが今日どれほど非効率的であるか想像してみてください。

あなたがJavaScript開発者であり、そして何らかの理由であなたがコードエディタのためにただ一つのプラグインを選ぶことを余儀なくされるならば、あなたはESLintを選ぶべきです。

あなたがコードを書く間に短いフィードバックを回し続けるツールは、追加することを歓迎するべきです。私たちが創り出すものに即座の視覚的表現を発明することについてのBret Victorの議論は私にとっては目を覚ますものでした。ツールを採用し改善することは、私たちをその明るい未来に導くための1つの方法です。今まで見たことがなければ、今すぐBretの話を見てください。

私が素晴らしい新しいツールを見つけたとき、私の唯一の後悔はそのツールを以前に使っていなかったことです。より良いツールは、あなたがより良いプログラマーになるのを助けます。それらを見つけ、使用し、評価し、そして可能であればそれらを改善してください。

言語の選択は重要です。型安全であることも重要です。JavaScriptに起こった最も良いことはTypeScript(そしてFlow)です。コードの静的解析はあなたが思うよりも大きなことです。あなたがそれをしていないのであれば、あなたは基本的に将来の未知のものに対して脆くしています。静的型付けシステムなしでコーディングしないでください。選択した言語に静的な型付けがない場合は、言語を変更するか、またはそのためのトランスパイラを見つけてください。今日のトランスパイラは、コード内のコメントを読むだけで動作するのに十分スマートです。ネイティブにサポートされていない言語の型チェックが将来行われると思います。

ソフトウェアエンジニアリングの進化

2ヶ月、6ヶ月、1年でソフトウェアエンジニアリングを学ぶことは誰にもできません。ソフトウェアエンジニアになるためにブートキャンプで学ばないでください。私は過去20年以上にわたって学んできましたが、今日もまだ学んでいます。私は、約10年の学習と数千人のユーザーによって使用されるアプリケーションの設計、構築、および保守の後に初めて、自分自身を経験豊富なプログラマーと呼ぶに足る自信を持つようになりました。

ソフトウェアエンジニアリングはすべての人のためのものではありませんが、誰もが自分のコンピュータに関する問題を解決することを学ぶべきです。あなたが簡単なプログラムを書くことを学べるならそうするべきです。あなたが一般的なソフトウェアサービスを使うことを学ぶことができるならばそうするべきです。あなたがオープンソースソフトウェアの使い方を学ぶことができるなら、あなたは多くの力を身につけられるでしょう。

問題は進化し、ソフトウェアエンジニアリングも進化します。この職業の将来は、一般的なコンピュータユーザーが5年間勉強しなくても自分のコンピュータを使用できるようにすることです。使いやすいツールを使用して、ユーザーが自分自身で簡単な問題を解決できるようにします。その後、ソフトウェアエンジニアは、より良いツールを作成し、より大きな既知の問題を解決し、未知の問題を防ぐために最善を尽くします。

読んでくれてありがとう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

switch文を使わないゲーム状態遷移inC++

はじめに

今回Qiitaに書き込みするのは初めてです。C++でゲームプログラミングやってるので、自分のやり方を書き残していこうと思います。
独学プログラマ なので、間違っていたり、少しばかり極端なことも書くことになるかと思いますので、C++やゲームプログラミングをやっている諸兄を憤慨させることになるかもしれませんが、その場合はできる限り冷静にご指摘いただけると助かります。
なお、検証はVisualStudio2017のC++コンパイラ及びWindows10の実行環境です。

ここでいう状態遷移とは

ここでいう状態遷移とは、ゲームの各画面(シーン)状態(タイトル画面→キャラセレなど→ゲーム本編→ゲームオーバーなど)の流れをコントロールするものを表しています。
statemachine.PNG
そのほかにもキャラクターのニュートラル→ジャンプ→着地→しゃがみ→攻撃→ダメージなどの状態遷移も扱います。

switch文を使わないってどういう事?

おそらくプログラミングを習い始めの時は、上記に書いたような状態遷移を扱う場合は状態を表すenum変数などを用意して、何かイベントが起きるたびに変数を変更し、switch分岐で処理を切り替えていたと思います。

switch( screenstate ){
case title_scene:
  //タイトル用の処理
  break;
case characterselect_scene:
  //キャラセレ用の処理
  break;
    :
    :
  (以下略)
}

全然それで間違っていないのですが、どうしてもこのswitch文を置いている関数ブロックが長くなってしまうのがあまり好きじゃないです。これが置かれるであろう関数はUpdate関数とかになると思いますが…
凝ったことをやろうとすると、どうしてもこの部分が複雑になっていってよくないです。というわけで僕のプログラムでは状態遷移にswitch文を使用しません。

じゃあなにを使用するのか?

じゃあどうやって状態遷移をするのかというと3つほど考えます

  • Stateパターンによる切り替え
  • メンバ関数ポインタによる切り替え
  • ラムダ式による切り替え(たまにしか使いませんが…)

もしほかに良い、カッコいい状態遷移をお知りの諸兄はコメント欄等で教えていただけると幸いです。

Stateパターンで状態遷移

ひとまずは、シーンの遷移をStateパターンで作ってみることを考えます。基底クラスをSceneとします。

遷移基底クラス

class Input;
class SceneController;
///シーン管理のための基底クラス
///(純粋仮想クラス)
class Scene
{
protected:
  SceneController& _controller;
public:
  Scene(SceneController& controller);
  virtual ~Scene();

  ///シーンの更新を行う
  virtual void Update(const Input& input) = 0;
  ///シーンの描画を行う
  virtual void Draw()= 0;
};

ちなみにInputは入力情報が入ったオブジェクトクラスです。SceneControllerは遷移を制御するクラスです。こいつを基底クラスに持たせることによって、各シーンは次のシーンへ飛ぶことができるようにしています。 ついでだからSceneControllerも書きますが

遷移制御クラス

class Scene;
class Input;
///シーン管理クラス
class SceneController
{
private:
  shared_ptr<Scene> _scene;
public:
  SceneController();
  ~SceneController();

  //各シーンのUpdateを呼び出す
  void SceneUpdate(const Input& input);

  //シーンを変更する
  void ChangeScene(Scene*);

};

ChangeSceneでシーンの変更を行うようにしています。さて、こうするとクラス・ファイルの数は増えますが、それぞれの状態を独立して管理でき、さらには状態遷移について管理するのは実は状態遷移前のクラスが状態遷移後のクラスを知ってればいいという事になります。

あとはかんたん

さて、あとはご想像の通り、Sceneを継承したクラスを作っていけば良いです。
classdiagram.PNG
ここまでができていれば後は、切り替え部分だけですが簡単です。SceneControllerのメンバ関数にChangeSceneがあったと思いますが、アレの中身は

void
SceneController::ChangeScene(Scene* scene) {
  _scene.reset(scene);
}

このようになっているとします。あとは呼び出し側がChangeSceneにnewして突っ込めば終わりですよね。初期シーンがタイトルシーンだとすると

SceneController::SceneController(){
  _scene.reset(new TitleScene(*this));
}

これでデフォルトがタイトルシーンとなります。thisを渡しているのはタイトルシーンじしんが次のシーンへの切り替えをできるようにするためです。

という事でタイトルシーンが切り替えする部分は

void 
TitleScene::FadeoutUpdate(const Input& input) {
  if (--_wait == 0) {
    _controller.ChangeScene(new CharacterSelectScene(_controller));
  }
  else {
    //フェードアウト処理コード(略)
  }
}

のようにします。タイトルがフェードした後に次の処理に来るように書いています。ちなみにしれっとスマートポインタを使用しているので元のシーンは自動的に消えます。

ちょっと改良

階層型メニューとか、ポーズ画面とかのように、遷移後に「前の画面に戻りたい」という事があるかと思います。結構やってる人も多いみたいなんで書くのは憚られるんですが、敢えて書きます。
状態遷移オブジェクトをスタック型で管理するという事です。

状態遷移をスタックで管理!?

そもそもスタックを知らない人もいるかと思いますが、FILO(First In Last Out)の構造です。どうするかをポーズ画面を例にとって説明します。

まず、ここまでの話をそのまま実装しているなら
1.PNG
こういう遷移になっているでしょうが、ポーズはポーズしたところでゲームオブジェクトを消してもまずいし、終わったら速やかにポーズ状態を解除して、ゲーム中に戻ってきてほしい…なのでスタックを使って
pausein.PNG
こうして
pauseout.PNG
こうしたい。

実装

さて、どう実装しようか?という話ですが、素直にstd::stackを使います。普通のStateパターンが実装できてるなら簡単ですよ~。
まず、SceneControllerをこう書き換えます。

#include<stack>
class Scene;
class Input;
///シーン管理クラス
class SceneController
{
private:
  std::stack<std::shared_ptr<Scene>> _scene;

public:
  SceneController();
  ~SceneController();

  void SceneUpdate(const Input& input);
  void ChangeScene(Scene*);
  void PushScene(Scene*);
  void PopScene();
};

さっきまでただのshared_ptr<Scene>だったのが、stack<shared_ptr<Scene>>になってるのが…わかるだろう?
まずはChangeSceneの挙動は「変えずに」スタックに対応させていきたい。となるとChangeScene

void
SceneController::ChangeScene(Scene* scene) {
  _scene.pop();
  _scene.emplace(scene);
}

こうなる。ちなみにemplaceというのは、新しい記法で、↑のは内容的には
_scene.push(shared_ptr<Scene>(scene));
と同じ結果になる。もともとあったシーンオブジェクトはpopしてしまって、新しいのをpushしてるんで、Changeと同じ意味になります。
さて、これで元のChangeSceneはできたわけだ。ちがうのはここからだ・・・です。PushScenePopSceneですが、こいつらも簡単です。

void
SceneController::PushScene(Scene* scene) {
  _scene.emplace(scene);
}

void
SceneController::PopScene() {
  _scene.pop();
  assert(!_scene.empty());
}

・・・工事完了です。ね?簡単でしょ?
あとは、ゲームプレイ中にポーズボタン押されたタイミングで、ポーズをPushSceneすればOK

void
GamePlayingScene::Update(const Input& input) {
  if (input.IsTriggered(0, "pause")) {
    _controller.PushScene(new PauseScene(_controller));
  }
  (以下略)
}

あ、PushとPauseがスペル似てるから、混乱しそうだけど間違えないでくださいね。Popはただpopするだけなので説明はしませんが、Pauseシーンが終わる際にpop命令出せばまた元のプレイシーンに戻るわけです。
はい、Stateパターンの状態遷移に関してはこのくらいです。

メンバ関数ポインタによる状態遷移

さて、Stateパターンくらいは知ってるよって人も多いんじゃないかなと思います。が、ここで考えるかもしれません。
「でもさぁ、シーンならともかくキャラクタの状態遷移にStateパターン使うのは大げさすぎ!やっぱりパパッとswitch文書いて…終わりでいいんじゃない?」
まぁ待て待て、まだ慌てるような時間じゃない

メンバ関数ポインタとは・・・?

そこで活用してみたいのはメンバ関数ポインタです。
メンバ関数ポインタじたい基本なはずですが、なんかマイナーなのか知らない人が多いみたいです。
メンバ関数ポインタとはその名の通りクラスのメンバ関数を指し示すポインタの事です。
通常の関数ポインタとちょっと違うので、注意してください。

基本構文

メンバ関数ポインタ宣言

戻り値 (クラス名::*変数名)(パラメータ);
ちょーっとややこしいですが、けったいな宣言ですね。メンバ指定子にアスタリスクがつくというなんだこれって感じの宣言になります。
実際のコードだとこんな感じですね
void (Player::*_updater)(const Input&);
これで宣言はOKなので、あとはメンバ関数の代入と実行だけです。自クラス(この例だとPlayerクラス)内で宣言する場合でも、所有クラス名::を忘れいないようにしなければならないので、注意してください。

メンバ関数ポインタにメンバ関数を代入

ポインタ変数=&クラス::メンバ関数名;
注意点は、&が付くところと所有クラスの名前::がやっぱり必要なところです。&を忘れがちなので注意しましょう。

メンバ関数ポインタで関数を実行

(オブジェクト名->*)(パラメータ);
文法はこうです。これはオブジェクトがポインタだった場合なので、オブジェクトが非ポインタなら
(オブジェクト名.*)(パラメータ);
になります。アスタリスクが変なつき方をしているので、注意しましょう。初めての人がみたら何やってんの?って感じです。実際のコードは

void 
PauseScene::Update(const Input& input) {
  (this->*_updater)(input);
}

こんな感じですね。Update関数はとにかく_updaterを実行して、状態によって_updaterの中身が切り替わっていくというイメージですね。

ラムダ式を使って状態遷移

正直これはおまけみたいなもんです。
前回のメンバ関数ポインタの話をラムダ式にしただけですね。
ラムダ式自体はご存知だと思います。
ただしメンバ変数に利用するにはもうちょっと知識が必要です。それはラムダ式の「型」がなんなのかという知識です。

いつもだったら、ラムダ式を代入する先の変数はauto指定をしていると思うんですが、これって型はどうなってるんでしょうか?

ラムダ式は関数オブジェクト(ファンクタ)に代入されます。さて、ファンクタの代入先の型はどう作ったらいいんでしょうか?

それはfunction型を使います。まず#include<functional>します。そのうえで

std::function< 戻り値(パラメータ) > ラムダ変数;

という感じで宣言します。このラムダ変数に対して、ラムダ式を代入することができますので、内容の切り替えが可能!!というわけです。

ラムダ変数 = [ バインド ]( パラメータ ){ 処理 };

で代入できます。で、今回みたいに状態遷移に利用したい場合には予めメンバのconstラムダ変数に初期化子で代入する事になります。例えば

const std::function<void(const Input& input)> lambda_fadein;
const std::function<void(const Input& input)> lambda_fadeout;

のように宣言しておいて、コンストラクタ初期化子(初期化リスト)で

PauseScene::PauseScene(SceneController& controller):Scene(controller),
lambda_fadein ([this](const Input& input) {
  ()
  --_frame;
}),
lambda_fadeout([this](const Input& input) {
  ()
  ++_frame;
}){
  ()
}

のように、初期化リストでlambda_fadeinやlamda_fadeoutなどの値を入れてしまいます。constがついてるから、ここでやらなければ怒られるんです。
それができたら、

std::function<void(const Input& input)> _lambda;

で宣言して、

_lambda=lambda_fadein;

などやって切り替えます。
ちなみに実行は、当然のように

_lambda(input);

です。簡単ですね。

それでは、switch文を使わない状態遷移に関しては以上です。読んでいただきありがとうございました。

注意点(およびご意見)

さっそくプログラマ諸兄からご指摘がありましたので記載しておきます。

  • 「メンバ関数ポインタは空関数があると、最適化の際に違う場所を指し示すことがあるよ」 とのこと

さて、空関数は当然のように意味がないため最適化された時に実体が省略されてるっぽい?飛び先としてのシンボルは存在するものの空関数を指し示しているはずの場所は次の関数となっており、予想外の挙動を示したとのこと。

一応、VisualStudio2017のReleaseモードでいくつかのパターンを試してみましたが、今のところ確認できていない。ご指摘いただいた兄貴曰く「コンパイラによるのかもしれない」とのこと、予想外の挙動をされても怖いので安全策としてvoid空関数ではなくboolかintあたりにして0かfalseを返すようにするか、volatile指定子をつけるかという対処になるかな。最適化についての挙動を確認できていないため、どこまで用心深くやるかは環境次第かなと。

  • メンバ関数ポインタだとデバッグ時に状態を確認できないため、enumでやったほうが分かりやすいのではないか?

とのこと。一応、VisualStudio2017の場合ならば現在の状態のメンバ関数はデバッガで確認できます。
しかしながら、これも環境によってはそんな親切な機能がない場合もありますので、開発環境次第という事ですね。
VisualStudioが神ツールであることがわかり、ありがたいご意見でした。世の中VisualStudioで開発できる環境ばかりでもないので、注意しましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【勝手に毎日投稿祭り】スイッチングハブを勢いで購入したので、物理サーバとWindows端末の通信に使ってみた

※1週間限定で、「勝手に毎日投稿祭り」と題して毎日投稿しています。
ぜひぜひ、皆さんも便乗して下さい。

スイッチングハブを使ったことありますか?

DSC_0415.JPG

私は、スイッチングハブやルータが、業務で使われているものは見てきたが、自分で設定をしたり、使ったことはない。
家庭にあるルータも業者が設定をしてから自分で何かいじることはなかった。ルーティングとかの勉強はしてもそれを活かせる実践の場がなかったのだ。
1189863.jpg

先日、夜勤作業があった帰りにECサイトを見ていたら、1000円程で手に入るのが分かり、夜勤明けでイケイケな気分だったこともあり、即購入ボタンを押していた。

「買ってしまった」

「もったいないし、ちゃんと使わなあかんやん」

と、いうことで特に下調べとかせずに買ってしまったスイッチングハブを使ってみることにした。
まず、開封すると説明書があった。

「ふむふむ、装置の名称ね、設置場所の注意点ね、接続できないときの確認ポイントね・・・、って後はないんかい!?」

操作説明とかあるかと思ったら、特に使い方の説明はない。しょうがないので、とりあえず、使ってみることにした。
思い付きで、買っていた為、LANケーブルも一本しかなく、先ほど買い足してとりあえず、こんな風にしてみた。
DSC_0417.JPG

配線の汚さは見なかったことにしてください。
今回は、物理サーバ(CentOS6.9)とPC端末(Windows)を繋いで、以下のように接続して使用します。
Image from Gyazo

まずは、同じネットワークで繋がるように、CentOS、Windowsそれぞれに以下のように設定をした。

CentOS6.9

/etc/sysconfig/network-scripts/ifcfg-eth0

ファイル内にIPADDRとNETMASKを追加し、ONBOOTとBOOTPROTOに関しては、以下のように書き換えた。

~略~
IPADDR=192.168.10.10
NETMASK=255.255.255.0

ONBOOT=yes
BOOTPROTO=none

ネットワークを再起動し、IPアドレスが適応されていることを確認

service network restart
ip addr

その他、vsftpdのパッケージをインストールし、サービスを立ち上げと自動起動設定をして、ファイアウォールとSElinuxの無効化を行った。

Windows

ネットワークとインターネットのネットワーク接続から、LANケーブルで使用するイーサネットのIPv4に設定する

Image from Gyazo

上記を右クリックし、プロパティをクリック→ネットワークプロトコルバージョン4(TCP/IPv4)をクリック→プロパティをクリック
すると、以下のようなダイアログが表示されるので、IPとサブネットマスクに設定を入力しOKボタンを押下
Image from Gyazo

では、これで使えるかやってみる

Windows側で、コマンドプロンプトを立ち上げ、まずは、pingが通るか

Image from Gyazo

通った!
次に、ftp接続ができるかやってみる。
Image from Gyazo
接続できた。
スイッチングハブ自体には、設定なく繋ぐだけで使えることが分かった。これから活用してみようと思う。
皆さんも、手ごろな価格で手に入るので、是非、試してみて下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む