WindowsでMinGWを使ったC++コンパイル環境構築手順
WindowsPCにC++のコンパイル環境を作ったので手順をまとめておく。
WindowsのC++コンパイル環境というとVisualStudioが有名だが、
今回は別のVisualStudioではなくMinGWを使いg++のコンパイラをインストールし環境をつくる。
テンプレートの実装はヘッダに書かないといけない
ここ10年ほどC++から離れていたので最近学びなおしをしている。
C++にはテンプレートという機能があるが、
これを学びなおすべくテスト用のクラスをテンプレートを使い実装してみた。
// Hoge.h template <class T> class Hoge { private: T m_hoge; public: T GetHoge(); void SetHoge(T hoge); };
// Hoge.cpp #include "./Hoge.h" template< class T> T Hoge<T>::GetHoge() { return m_hoge; } template< class T> void Hoge<T>::SetHoge(T hoge) { m_hoge = hoge; }
// main.cpp int main() { Hoge<int> a; a.SetHoge(1); return 0; }
実装が終わったので、さっそくメイクしてみたところ以下のようなエラーが出た。
どうやらコンパイルまでは通っているが、リンク時にメソッドの実態定義が見つからずにエラーになっているらしい。
c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: main.o:main.cpp:(.text+0x23): undefined reference to `Hoge<int>::SetHoge(int)' c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: main.o:main.cpp:(.text+0x30): undefined reference to `Hoge<int>::GetHoge()' collect2.exe: error: ld returned 1 exit status Makefile:2: recipe for target 'all' failed mingw32-make: *** [all] Error 1
原因について調べてみるとこちらのブログで非常に参考になる内容が紹介されていた。
pknight.hatenablog.com
どうやらテンプレートを使用する場合、
実装については基本的にヘッダに書くのが流儀らしい。
ということでヘッダに定義内容を移したらすんなりリンクまで通った。
// Hoge.h template <class T> class Hoge { private: T m_hoge; public: T GetHoge() { return m_hoge; } // 定義もここに書く。 void SetHoge(T hoge) { m_hoge = hoge; } // 定義もここに書く。 };
VisualStudioCodeで定義先へ移動&元の位置へカンタンに戻る方法
コードを書いていると関数の定義先へ移動し内容を確認した後、
元の位置(関数の呼び出し元)へ戻るということをよくやる。
VisualStudioCodeにもこのショートカットキーあるはずだと思い調べたら見つかったので、
備忘録として残す。
定義元へ移動:F12
元の位置へ戻る:Alt + ←
asと()キャストの正しい使い分け方法
asキャストとは
C#ではCやC++にはなかったas演算子を使ってキャストをする機能が追加された。
使い方としてはキャストしたい対象のオブジェクトの後ろにasを付けるだけ。
SampleClass temp = object as SampleClass
キャスト後のふるまいは()でもasでも変わらない。
asと()キャストの違い
書き方の違いだけならどちら使ってもよいのでは?と思うかもしれないが、仕様も違うので正しく理解しておく必要がある。
違い①:キャスト失敗時のふるまい
()キャストで失敗した場合、InvalidCastExceptionの例外が発生する。
そのためキャスト失敗する可能性があるのであればcatch処理を実装しておく必要がある。
try { SampleClass temp = object as SampleClass } catch (InvalidCastException) { // エラー処理 }
ではasキャストの場合はどうかというと、例外は発生しない。
失敗した場合キャスト後のオブジェクトがnullになる仕様なのでnullチェックをすれば良い。
SampleClass temp = object as SampleClass if (temp == null) { // エラー処理 }
違い②:キャストできる対象のオブジェクト
もう1つの違いとしてasキャストは参照型のオブジェクトに対してしか適用できない。
そのためintやlong等の値型のオブジェクトではasは使えないので、キャスト時は()を使うしかない。
正しい使い分け方法
キャスト仕様の違いをふまえた上でではどのように使い分けたらよいのかというと、以下のように使い分けるのが良いと思う。
- 値型の場合:()キャストを使う
- 参照型の場合:asキャストを使う
理由としてはコードがシンプルになるからだ。
キャストというのは頻繁に使う処理なので、参照型に()キャストをしてすべてのキャストしている箇所に例外補足処理を入れるのはかなり面倒。しかも例外補足処理というのはよく忘れがちになることが多い。
それならasキャストでnullチェックをするだけの方がシンプルでラクだからだ。
SMB2.0が原因で共有フォルダアクセスが遅い場合の対処法
Windows10ではSMB2.0というファイル転送プロトコルが使われている。
このSMB2.0だがネットワークの負荷を軽減させるためにクライアント側にキャッシュを持たせる機能が入っており、共有フォルダへのアクセスが遅くなる場合がある。
デフォルトは10s毎にサーバーと同期を取りキャッシュ更新するので、最大10s程度は共有フォルダへのアクセスが遅くなる場合があるらしい。
<対処法>
レジストリエディタを開き以下の設定をする。
キー : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters
タイプ : DWORD
FileInfoCacheLifetime 0
FileNotFoundCacheLifetime 0
DirectoryCacheLifetime 0
詳細は以下のサイト
support.microsoft.com
Roundメソッドを使った正しい四捨五入の実装方法
C#の標準クラスではMath.Roundという数値の四捨五入に使えるメソッドがある。
docs.microsoft.com
このMath.Roundメソッドだがそのまま何も考えずに四捨五入で使うと問題が起きる。
例えば以下のようなケース。
Console.WriteLine(Math.Round(1.5)); Console.WriteLine(Math.Round(2.5)); Console.WriteLine(Math.Round(3.5)); Console.WriteLine(Math.Round(4.5));
Microsoftのページをよく読んでいないとすべて繰り上げされると思いがちだが、実際は以下のような結果になる。
2 2 4 4
2.5は四捨五入したら3、4.5は5で出力されてほしいが意図通りならない。
なぜこのような結果になるかというと、Math.Roundに第2引数を指定しない場合のデフォルトの動作は
「10 進数の値は最も近い整数値に丸められ、中間値は最も近い偶数値に丸められる」という仕様だからだ。
小学校の時に習った単純な四捨五入とは違う動作がデフォルトということだ。
これを正しく実装するには第2引数にMidpointRounding.AwayFromZeroを追加しメソッドを使う必要がある。
Console.WriteLine(Math.Round(1.5, MidpointRounding.AwayFromZero)); Console.WriteLine(Math.Round(2.5, MidpointRounding.AwayFromZero)); Console.WriteLine(Math.Round(3.5, MidpointRounding.AwayFromZero)); Console.WriteLine(Math.Round(4.5, MidpointRounding.AwayFromZero));
これで実装すると以下のように全て繰り上げて処理される。
2 3 4 5
Enumの要素数を1行で取得する方法
実装時にEnumの要素数を使って何かをしたいケースがよくある。
Enum自体は配列等ではないので、そのままでは要素数にあたるメソッド等は持っていない。
でも1行でカンタンに要素数取得する方法がある。
例えばこんなenumの要素数を取得する場合
public enum FruitsEnum { Apple, Banana, Strawberry }
これでカンタンに取得できる。
int length = Enum.GetValues(typeof(FruitsEnum)).Length;
DataGridViewTextBoxにテキストと画像を表示させる方法
DataGridViewTextBoxはテキストだけ表示するのが普通だが、セル内にテキストに加えて画像も一緒に描画したりすることができる。
DataGridViewのCellPaintingイベントで自力描画させれば割とカンタン。
セルを選択時は晴れ、非選択時は曇り画像を表示させるようにしてみた。
まずは画像ファイルをリソースに登録。

※使用したのは縦横64pixelの画像ファイル
次にCellPaintingのイベントを以下のように実装。
private void _dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if ((0 <= e.RowIndex) && (0 <= e.ColumnIndex)) { Image image; if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected) { image = Resources.fine; } else { image = Resources.rain; } Rectangle drawBounds = new Rectangle(e.CellBounds.Right - e.CellBounds.Height, e.CellBounds.Top, e.CellBounds.Height, e.CellBounds.Height); e.Paint(e.CellBounds, DataGridViewPaintParts.All); e.Graphics.DrawImage(image, drawBounds); //描画完了通知 e.Handled = true; } }
これで実行してみる。
選択したセルは晴れ画像が表示されて、非選択は曇り画像がセルの右側に表示される。

DataGridViewComboBoxCellを1クリックでドロップダウンさせる方法
DataGridViewの実装でDataGridViewComboBoxCellを使うことがたまにある。
このコンボボックスは普通のSystem.Windows.FormsのComboBoxと仕様が違い、デフォルトだと1クリックではリストがドロップダウンしてくれないのだ。

2回クリックして初めてドロップダウンするのがデフォルトの仕様らしい。
これだとユーザーはとってもめんどくさいので、これを1クリックでドロップダウンするよう実装する。
まずはDataGridViewのイベントに以下を追加。
_dataGridView.CellEnter += _dataGridView_CellEnter;
その後にイベント処理で以下のように実装する。
private void _dataGridView_CellEnter(object sender, DataGridViewCellEventArgs e) { if (_dataGridView[e.ColumnIndex, e.RowIndex].GetType().Equals(typeof(DataGridViewComboBoxCell))) { _dataGridView.BeginEdit(false); ((DataGridViewComboBoxEditingControl)_dataGridView.EditingControl).DroppedDown = true; } }
これで1クリックでコンボボックスのリストがドロップダウンしてくれるようになった。
数字以外が入力で色が変わるTextBoxの実装方法
テキストボックスに指定した文字(数字等)が入力されたら背景色を変えて、ユーザーにルール違反していることを通知したいことがある。
テキストボックスのイベントに少し処理追加すればカンタンに実装することができる。
public partial class Form1 : Form { private readonly Color _defaultBackColor; public Form1() { InitializeComponent(); _defaultBackColor = textBox1.BackColor; } private void textBox1_TextChanged(object sender, EventArgs e) { bool error = false; foreach(char c in textBox1.Text) { if (!char.IsDigit(c)) { error = true; break; } } if (error) { textBox1.BackColor = Color.Red; } else { textBox1.BackColor = _defaultBackColor; } } }
こんな感じで実装すると数字以外が入力された時に背景色が赤くなってお知らせできる。


数字以外そもそも入力されたくないならNumericUpDownコントロールを使うという手もあるが、数字だけじゃなく小文字英数字だけ許可という場合でも少し処理を変えるかけでカンタンに実装できる。






