聖根のアジト:メモ:プログラミングメモ:C++関連

プログラミングメモ:C++関連

コンストラクタ呼出前に参照される例
負の数の立方根
atan2と0
sdfやipchの出力位置変更
Win8.1の環境でのlpstrDefExt
HALFTONEは中間色ではない
constポインタの参照エラー
CreateFontのface
関数分割によるビルド速度改善
伸縮と法線
射影行列
スペキュラ光
DirextX9Graphics初期化
DirextX9設定
VC++6.0→2013の引継ぎ
IMEの無効化
基底クラスオブジェクトから派生クラスオブジェクトへの代入
コンストラクタ・デストラクタの手動呼び出し
切り詰め代入の警告漏れ
未初期化変数参照の警告漏れ
関数の皮をかぶったマクロに注意
virtualの欠点
new[0]の非推奨
配列引数における注意
条件演算子を用いた参照に関する注意


コンストラクタ呼出前に参照される例

C++において、オブジェクトを宣言するとコンストラクタが実行されるが、
グローバルで宣言したオブジェクトのコンストラクタの中では、
他の未だコンストラクタが呼び出されていないオブジェクトを参照する事ができる。
即ち、初期化されていないオブジェクトを参照することによる不具合が生じ得る。
複数のグローバルオブジェクトを宣言する場合には注意が要る。

2024.2.4


負の数の立方根

-1の立方根(の内の実数解)は通常、-1となるが、
C++の場合、pow(-1,1.0/3.0)で立方根を求めようとしても、
負の数の平方根を求めようとした時と同様、結果の値が「-1.#ind」となってしまう。

2023.10.8


atan2と0

Cでatan2を使うにあたって、
atan2(0.0,-1.0)とatan2(-0.0,-1.0)の結果に違いがある事に注意が要る。
前者はπを、後者は-πを返す。
一方、0.0==-0.0は真と判定される。

2023.9.30


DirectXの最新のSDKでは、DirectMusic関連をコンパイルできず、なおかつ古いSDKが配布されてないのが非常に困る。
下位互換は保たれるという話じゃなかったのか…。
仕方なくDirectX8のSDKを併用している。
コンパイルができなくなるって結構不便な事だし、その辺は互換を保って欲しかった。

それにMIDIって軽くてバックアップも取り易いのが依然かなり強みだと思うし、
音色やピッチのみを変えるという事も原理的にはできそうだし、見直されないものか…。
フリゲでも、最近のものは音声ファイルのサイズが膨大なものが多く、
バックアップを取る事が困難になってるのがわりと難題になってると思うんだけどなぁ。

2020.3.7


sdfやipchの出力位置変更

バックアップを取るためにsdfやipchが邪魔で、なんとか出力位置だけでも変えられないものかと思ってググってみたら、
幾つかのサイトで方法が紹介されていた。
https://shibamu.hatenadiary.org/entry/20101014/p1

その通りに、「オプション」→「テキスト エディター」→「C/C++」→「詳細」の所で、
「常にフォールバック位置を使用」をTrueにし、「フォールバック位置」に絶対パスを指定としたら上手く行った。
相対パスを指定しようとしても上手くいかなかった。

2020.2.19


昔作ったエディタが起動した瞬間に正常に表示されない問題が発生したが、
どうも昔はWM_SIZEがWM_PAINTの先に出てたのが、後に出るようになってたみたいだ。

2020.2.19


Win8.1の環境でのlpstrDefExt

Win8.1の環境では、GetSaveFileName等で初期ディレクトリを指定できるはずの
lpstrDefExtが、なぜか反映されなくなってるようだ。
カレントディレクトリとは別に、Windows側でソフト毎に記憶されてるものが、
GetSaveFileName等の初期ディレクトリとなってるようだ。
lpstrFileにパス付きファイル名を指定すれば、そのフォルダが初期フォルダになるのだが、
パス名だけではGetSaveFileName等が失敗してしまって苦しい。

2019.10.8


Direct3Dではディフューズ色やアンビエント色の計算はテクスチャ合成前に行われて、
スペキュラ色の計算だけ後に行われてるようだ。
テクスチャ有りで陰影を付けたい場合は、白地に不透明なものを乗算にするしかないのかな。
この辺の処理の順番なんて簡単に変えられそうなのに、案外ググっても話題にすらなってない。
この辺はもう古い機能なのかな…。

2019.5.16


HALFTONEは中間色ではない

HALFTONEモードでのStretchBlt、どうも単純に中間色を取ってるわけでは無いようだ。
暗い緑と黒しか無い画像を縮めた際、どう平均を取っても出ないはずの明るい緑が、
色の境界辺りに出て来る。
逆に、白と他の色との境界が、他の色より暗くなる場合もある。
境界がはっきるするためか、単純に中間色を取る場合と比べて綺麗になる場合もあるが、
若干汚い縮小になってしまう場合がある。
両方のモードが用意されていれば便利なのに…。

2019.5.13


constポインタの参照エラー

ポインタの参照型を扱う際、謎の厄介な問題がある。

func(const char *(&str));

と定義した関数に対し、

char *str;
func(str);

とするとコンパイルエラーになってしまう。

func(char *(&str));

という宣言ならば通るが、今度は当然ながら、

const char *str;
func(str);

で上手く行かない。
対策は無いものか探していた所、七空白のブログさんのこのページで同様の指摘と、それに対する対策が記されてるのを見つける。
今回の例の場合は、

func(*(const char **)&str);

とする事で上手く行った(面倒臭いが…)。

これらは、もう少し本質的な問題としては、
const char *str;
char **str2 = &str;

char *str;
const char **str2 = &str;
が各々、「'const char **' から 'char **' に変換できません」
「'char **' から 'const char **' に変換できません」
というエラーを出す事が挙げられると思われる。

これらは、各々以下のようにキャストすると通る。
const char *str;
char **str2 = (char **)&str;

char *str;
const char **str2 = (const char **)&str;

2018.12.8-2022.10.4


CreateFontのface

CreateFontは
CreateFont(16, 0, 0, 0, 0, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"System");
と設定すると、何もしてない時と同じ文字フォントになるようだ。
feceにNULLを指定した場合はMS UI Gothicと解釈されるようだ。
また、Systemでは中途半端な拡大ができない。

2018.9.12


関数分割によるビルド速度改善

プログラムのビルド時に「コード生成しています」で何分も待たされる事に悩まされていた。
SDLチェックをOFFにしてみたが全く効果無かった。
超大になってた関数を幾つかに分けた事で普通な速度に戻った。
SDLチェックをONに戻しても問題無かった。

2018.3.18


伸縮と法線

ワールド座標変換に伸縮(D3DXMatrixScaling)を加えた場合、
ライトの影響、特にスペキュラの影響が大きく変化してしまう。
どうも座標変換の際に法線の大きさが変わってしまってるようで、
それが原因の様子。
SetRenderStateでD3DRS_NORMALIZENORMALSをTRUEにすれば一応解決する。

ここで気になったのは、立体を縮める操作を法線にも適用したとしたら、
法線ベクトルの絶対値も小さくなり、すると光は暗くなるはずなのに、
逆に明るくなっている点。
どうも、小さくしない代わりにわざわざ大きくしているようなのだ。
D3DRS_NORMALIZENORMALSで負荷を掛け損な気がしてしまう。

これは、物体を伸縮する場合のための変換のようだ。
物体を全体的に大きくする場合には法線をいじる必要は無いが、
縦だけに引き伸ばすなどの場合はそうは行かない。
後は内部的にどう計算してるかかな。

2016.5.7


射影行列

射影行列は、D3DXMatrixPerspectiveFovLHが解り易い。
でも、正面から見た状態を同じ画面内の違う位置に貼りたい場合は、
D3DXMatrixPerspectiveOffCenterLHが便利。
(もっと良い方法有りそうな気もするけど…)
b - t = 2 * tan(fovY / 2);
r - l = (b - t) * Aspect;
で変換で来たと思う。
後はrとl、bとtの値に差を設けてやる事で、表示位置だけをずらす事ができる。

2016.5.5


スペキュラ光

スペキュラ光は…
SetRenderStateでD3DRS_SPECULARENABLEをTRUEにしないと発動しない!
頂点情報にスペキュラ色を加える必要は無い。

2016.5.5


DirextX9Graphics初期化

なんとなくDirextX9Graphicsを触ってみようと思ったら、極めて基本的な所で悪戦苦闘した。
書いてある通りにやってるのに上手く行かない。

まず、2次元の例までは表示されるのに3次元が出ない。
座標の中心が画面の中心に移ってるため、画面外に近い位置になってしまってた上に、
裏と表が逆転していた。
XYZRHW仕様からXYZ仕様に移行し、3種の行列をちゃんと指定する事も重要。

次に、ライトがつかない。
これについては、(マテリアルの設定に加え)頂点要素に法線ベクトルを含め設定する事で解決した。

更にZバッファが全く機能しない。
これについては、D3DXMatrixPerspectiveFovLHの近クリップ面に0.0fを指定してたのを1.0fに
変えたら上手く行った。

ついでにBackBufferのデバイスコンテキストが取得できない。
D3DPRESENT_PARAMETERSのFlagsにD3DPRESENTFLAG_LOCKABLE_BACKBUFFERを指定したら正常に機能した。

2016.4.20-21


DirextX9設定

なんとなくDirextX9を触ってみようと思い、SDKインストールしてみた所エラーがでた。
http://nanoappli.com/blog/archives/4739
の通りにやったら上手く行った(最後の処理はスタート画面→PC設定→保守と管理→Windows Updateの所で自動的に出てた)。
「d3dxof.libが開けません!」言われたので調べてみたが、
言われた通りに設定しても上手く行かない…と悩んでいた所、
どうも昔のSDKとは異なり、Lib/ではなくLib/x86/を指定しなければいけなかったようだ。
「追加の〜」の方を指定しなくても、VC++ディレクトリの方を指定しただけで動いた。

2015.7.17


VC++6.0→2013の引継ぎ

勝手にWindows8.1にされてしまったために6.0が動かなくなってしまったが、
過去のソフトを久々に改造したくなり、観念してExpress(2013 for Windows Desktop)導入した。
デフォルトでは、これをインストールしてない環境での起動にはランタイムが要る設定になってるみたいで、
ランタイム要らないようにするには、(Rleaseにした上で)プロジェクトを右クリックし、
プロパティ→構成プロパティ→C/C++→コード生成の所で、ランタイムライブラリを
マルチスレッド(/MT)にしておく必要がある。
親父が先に導入してなかったら気付かなかった。危ない。
尤も、引き継ぎプロジェクトの場合は、最初からこの設定になってた。
作業用ディレクトリは設定し直す必要があった。

また、DirextXのディレクトリ指定法が変わり、プロジェクト毎になってた
(構成プロパティ→VC++ディレクトリの所の、インクルードディレクトリで指定)。
DirextX8SDKの場合、指定しただけではエラーが出たが、〜 陽射し 〜さんに従い、
インクルードディレクトリの所で、
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include
(バージョンの違いからか、パスが少し異なってた)
をDirextXの物の前に指定したら、警告は出たものの解決した。

また、サウンドロード用に本付属サンプルのコードを使っていたが、そこのfstream.hがインクルードできなくなってた。
.hを取ればインクルードできたが、end⇒ios::endのような書き換えが必要のようで、しかもいまいちうまく動作しなかった。
後のソフトではfstreamを使わない形式に書き直してたが、そちらを使ったら正常に動作した。
DX8のDirextAudioを最初試してみたんだが、特定のMIDIファイルを鳴らそうとしたり、
セカンダリで効果音を鳴らそうとすると、特定の効果音を超連打しようとすると落ちる問題があった。
前者は、MIDIファイルを最新版に置き換える事で、後者は、連打を控える安全装置を付ける事で解消されたが、
先の方法で解決する事が判ったのでDX7仕様に戻した。

6.0からの引き継ぎは、.dswを開こうとしたら自動的にやってくれた。
ただ、その後からは.vcxprojあるいは.slnを開くようにしないといけない。
また.dswを開こうとしてOKとかやると、折角設定し直したプロジェクトの設定を再度やり直さなければ
いけない(何度もやり直してしまった…)。

2015.1.12


ひいいエクスプレスさん
}else{
を勝手に
}
else{
に書き直すのやめてくだしあ。

2015.1.7

ツール→オプション→テキストエディター→C/C++→書式設定→改行で、「新しい行に'else'を設置」をOFFで解決しました。

2015.2.19


long doubleって型がちゃんとあるにも関わらず、double型と全く同じなのが実に残念。
最近のエクスプレスでも未だ同じらしい。
四倍精度化しているか、少なくとも拡張倍精度化していれば、移行する価値もあると思うんだけどねぇ。
double型じゃint64型との相性がな…。1.0を掛けただけで値が変わってしまう事があるよ。
RPGの経験値の値とか、ちょっと変な事をしようとするとすぐlongの限界を超えてしまうからね。
自前での精度対策は面倒だし心許無い。

2014.10.22


newが失敗している、なぜだ、と思って散々悩んでたら、直前のif構文(消し忘れ)の後の;を忘れていたせいで、
確保の処理そのものがされてないだけだった。

2014.10.9


同じ生の文字列("abc"等)を複数回使用する際、自分の使っているVC++6.0では、
文字列毎にアドレスが異なっているが、最近の物では、同じアドレスになっている模様。
つまり、自分の使っている物では、全く同じ文字列が、同一であるという判定がなされず、
書かれた回数だけ組み込まれている模様。
設定にもよるのかな。

2014.10.1


特定の関数を書こうとすると開発ツール自体が必ず強制終了されてしまう問題が起こって焦ったが、
ncbファイルを除外したら、特に弊害も無く収まったみたい。

2014.9.27


IMEの無効化

ゲームなど作る際、そのままでは、半角/全角キーを押すと、キーを押すたびにウインドウの左上隅などに文字列が表示され、非常に鬱陶しい事になります。
これだけなら、半角/全角キーを押さなければ済む話だったのですが、環境によっては、全角/半角状態が全てのウインドウで共通となります。
これにより、他のウインドウで全角で作業していた場合に、ゲームのウインドウに移ると、そのまま全角状態になってしまいます。

これに対し、メインウインドウのプロシージャにWM_IME_SETCONTEXTが送られて来るので、こいつを拾って無視してやると、この問題をとりあえず解決できました。
原理は良くわかりません^^;。
WM_IME_STARTCOMPOSITIONでも無効化できますが、文字を変換するような手順を行うと、右下辺りに変換候補のウインドウが出て来てしまいます。
WM_IME_SETCONTEXTを無視した場合が、純粋に表示系をカットする感じなのに対し、こちらの場合は、WM_CHARに送られて来る文字列については、IMEを無視した形となります。
WM_IME_COMPOSITIONでも無効化できましたが、左上隅に空の変換バーのようなものが表示される事があります。
いずれの場合も、裏ではちゃんと稼働しているようで、ImmGetCompositionStringを使うと、入力途中の文字列を取得する事ができますし、IME系メッセージは送られ続けるようです。

一方、IME_SETOPENなどを使ってIMEを閉じるという方法も有りますが、逆に全角で作業していたウインドウに戻った際、半角になってて若干不便です。
また、扱いが難しいようで、いまいち思うように動いてくれませんでした。

2014.9.19


基底クラスオブジェクトから派生クラスオブジェクトへの代入

なぜか「基底から派生への代入は自分で組むしかない」と言われる事が多いようだが、実は大嘘。

派生クラスobj.基底クラス名::operator=(基底クラスobj)

で問題無く代入できる。
代入操作は、デフォルトでもoperator=という関数の形で定義されているため、基底クラスのこれを関数形式で呼び出すわけ。
他、

*(基底クラス名 *)&派生クラスobj=基底クラスobj

でもなぜか問題なく動くが、この場合は、派生クラスの所に、この基底クラスを持たないobjを指定した場合でも
コンパイルが通ってしまうため、潜在不具合の危険大(2014.3.23までは、前述の方法に気付かなかったため、
こちらを利用していたのだが、自分でもこの不具合を起こしてしまった)。

ともあれ、派生クラスのobjには、基底クラスのobjが(メンバobjと同様に)しっかり含まれてるのに、
それを参照する術が無いというのは謎であり、少し不便に思う。
obj.基底クラス名::thisみたいな事ができても良さそうなのに。
そういう必要がある場合は、基底でなくメンバにした方が良いのだろうか。

2013.5.21-2014.4.20


コンストラクタ・デストラクタの手動呼び出し

「コンストラクタから別のコンストラクタを呼び出す事は出来ない」と言われる事があるが、
クラス名::クラス名()という形でなら普通に呼び出せる様子。
クラス外からでもオブジェクト名.クラス名::クラス名()の形で呼び出せる。
デストラクタも同様に呼び出せるが、デストラクタの場合はthis->~クラス名()の形でも呼び出せる様子。
ただ、初期化の処理も行われ、constのメンバも変わってしまうので、かなり邪道かもしれない。

2014.3.23


引数の無いコンストラクタはデフォルトコンストラクタと呼ばれる。
自動的に作られるコピーコンストラクタは、デフォルトコピーコンストラクタと呼ばれる。
じゃあ自動的に作られるデフォルトコンストラクタは何だ?
デフォルトデフォルトコンストラクタか?

2014.3.23


切り詰め代入の警告漏れ

long型をshort型やchar型に直接代入しようとすると警告されるが、最後に+0などを付けると警告されない。
short型⇒char型の場合、途中に付けた場合でも警告されない。
unsigned long型からの場合は警告される。
signedとunsignedを比較しようとすると警告されるが、代入しようとした場合は警告されない。

2014.3.17-23


未初期化変数参照の警告漏れ

通常、値を割り当てられてない変数を参照しようとすると、ビルド時に警告される。
しかし、
int a;for(;;)if(a && (a=0));
あるいは
int a;while(1)if(a && (a=0));
のように書いた場合、6.0では警告が表示されない。

2014.3.7


派生クラスと基底クラスで同名の関数が有る場合、
「派生クラスで引数が同クラス」>「派生クラスで引数が基底クラス」>「基底クラスで引数が同クラス」
で優先される。

2013.10.16


関数の皮をかぶったマクロに注意

maxとminは関数の皮を被ったマクロなので要注意。
max(i++,5);とすると、iが2つ増えてしまう事がある。
計算に時間の掛かるような関数を入れるのも具合が悪い。
#include <windows.h>の前に#define NOMINMAXを入れて「リビルド」すれば定義されない様子。

CopyMemoryとZeroMemoryもマクロだが、実質的にはそれぞれmemsetとmemcpyを呼び出してるだけみたいだから害は無さそう。

2013.10.15


virtualの欠点

仮想デストラクタを持つクラスをZeroMemoryするのはアウト?
仮想デストラクタを持つ構造体をnewで取得し、構造体のサイズでZeroMemoryしてから
deleteすると、強制終了となる事が有った。
強制終了はデストラクタに入る前に起こる。デストラクタが空でも起こる。
基底クラスにはデストラクタ無し。

以下のサイトによると、仮想関数自体を含んでいる場合が問題らしい。
http://hexadrive.sblo.jp/article/29413823.html
便利な仮想関数だが、使用には注意を要する。

また、メンバ関数を実装していなくても、そのメンバ関数を使いさえしてなければ怒られないが、
virtual付きでメンバ関数を定義してしまうと、実装しなければ、そのオブジェクトを定義できなくなる。
これにより、同一のヘッダを複数のプログラムで参照している場合に障害になる事がある。

2013.5.29-2014.9.27


new[0]の非推奨

自分の環境では、仮想デストラクタを持つクラス/構造体を、
newで要素数0で取得すると、アドレスは格納されるが、
これに対してdeleteないしdelete[]を実行すると、ランタイムエラーが発生する。
この場合に限らず、要素数0でnew呼び出しは避けるのが無難か?

2013.5.21


ファイル入出力で低水準の方が余分な物が無い分早いかと思ってたら逆なのか。
高水準は高速化の為の工夫をしてくれてたらしい。
http://www5c.biglobe.ne.jp/~ecb/c/13_01.html
http://www5c.biglobe.ne.jp/~ecb/c/12_01.html
http://d.hatena.ne.jp/skyjoker/20130102/1357093289
低水準を使うなら、read/writeを変数ごとに呼び出すのでは無く、メモリに溜めこんで
一回で読み書きするのが得策なのかな。

2013.5.15


配列引数における注意

void func(char a[10])

のように、引数に配列を渡す場合、配列の参照が渡されてしまう。
すなわち、関数内で配列要素の値を変更すると、呼び出し元の配列まで変わってしまう
これにより、幾らa[10]という宣言をしていても、要素数がこれより少ない配列を渡してしまうと、
a[9]=5などとした場合にランタイムエラーが発生する事がある。

2013.5.21


派生と基底とデストラクタ

継承を持つクラスのオブジェクトを定義、またはnewで取得した場合、基底のコンストラクタ達が先に呼び出され、
次にメンバクラスのコンストラクタ、最後に自分のコンストラクタが呼び出される。
スコープアウトやdeleteによりオブジェクトが破棄された場合は逆に、自分のデストラクタが先に呼び出され、
次にメンバのデストラクタ、基底のデストラクタとなる。
(malloc〜freeの場合、どちらも呼び出されない)

しかし、newで取得した派生クラスのメンバのポインタを基底クラスに代入し、それをdeleteした場合、
基底のデストラクタ(および基底の持つメンバのデストラクタ)しか呼び出されない。
基底クラスのデストラクタの宣言の前にvirtualを付ける事により、その場合でも派生のデストラクタが
呼び出されるようになる。
派生の更に派生を作る場合、最も基底のものにさえvirtualが付いていれば、それも呼び出される様子だが…。
派生にしかvirtualが無い場合はdelete時に強制終了したりする。

コピーコンストラクタの場合、コピーコンストラクタは(代入演算子同様)自分の物のみが呼び出される。
デフォルトコピーコンストラクタは、全てのメンバクラス・基底クラスに対し、それぞれのコピーコンストラクタを呼び出す。
自分でコピーコンストラクタを作った際、自分で初期化の記述(≠代入)を行わないと、メンバや基底に対しては
デフォルトコンストラクタが呼び出される。

2013.5.21-2014.3.23


基底クラスのメンバを参照する場合、
基底クラス名::メンバ
とする。

2013.5.21


条件演算子を用いた参照に関する注意

参照を宣言する際、
基底クラス名 &ref=条件?派生クラスobj:派生クラスobj;

基底クラス名 &ref=条件?基底クラスobj:基底クラスobj;
なら問題無いが、
基底クラス名 &ref=条件?基底クラスobj:派生クラスobj;
とすると問題がある。これは、
基底クラス名 &ref=条件?基底クラスobj:(基底クラス名)派生クラスobj;
のように認識されるが、このキャストの際に、一時的なクラスオブジェクトが作成され、
目的のobjの参照では無くなってしまう。
基底クラス名 &ref=条件?基底クラスobj:*(基底クラス名 *)&派生クラスobj;
とすると一応大丈夫。

2013.5.21


オブジェクトを引数・戻り値で受け渡す方法の定義

クラス・構造体を引数として渡す時、及び戻り値として返す時、
コピーコンストラクタが呼び出されている。
コピーコンストラクタは
クラス名(const クラス名 &obj);
で定義できる。&が無いとコンパイルエラー。
constは無くてもコピーコンストラクタとして認識されるが、constなオブジェクトを渡せなくなる。
両方定義すると警告が発生。

2013.5.21


プログラミングメモに戻る
メモに戻る
トップに戻る