何か昔同じタイトルの記事を見たことあるって人へ
この記事は昔やってたブログの記事の移植です。
昔の内容で有用そうなものをQiitaに少し修正を加えて移植してます(現在進行形で)。
概要
コンパイラの警告として
warning C4345: 動作変更 : 形式 () の初期化子で構築される POD 型のオブジェクトは既定初期化されます。
って奴が出る事がある。
自分の場合は殆どのケースでboost::variantを使用してこの警告が出る。
これは何を意味するのかって話。
上記警告を出す為のコード例
void main()
{
struct AAA{ int a; };
AAA test;
AAA* pTest = new ( &test ) AAA(); // ここでC4345.
}
【前提知識1】プレースメントnew(配置new)
この警告が出るということはプレースメントnew使ってるんだから説明不要に見えるけどboost::variantの場合内部で使ってて警告がそこで出るので一応解説する(プレースメントnewを使ってない場合でも出るかに関しては未調査)。
コード例の5行目の初期化方法は見慣れていない人もいると思う。
AAA* pTest = new ( &test ) AAA();
この初期化方法をプレースメントnewというんだけど「配置new」と言えばそれなりに判りやすいかもしれない。
丁寧に解説するの面倒だからざっくり言うけど、ようはnewする際に割り当てるポインタを自分で指定できる。ようは先に確保しておいたメモリに対して配置するnewって事。
今回は[ pTest ]に[ &test ]のポインタを[ new ]で割り当ててる。
「普通にpTest = &testでよくね?」
ごもっともです。上記のコードは例なのでそこは勘弁。
本来の使い方はメモリプールを別途用意しておいて、そのメモリプールをブロック毎に区切って配置する事でメモリ確保のオーバーヘッドを減らしたりする。
あんまり使わないね(ゲームのタスクリストとかで高速化に使ったことあるけど最近のPCは早すぎて効果無かったよ)。
【前提知識2】POD型
昔Cの参考書とか読んで構造体は次のように初期化できると見たことがあると思う
void main()
{
struct AAA { int a; char b; };
// この{}で括った初期化方法
AAA const test = { 100, 'A' };
return 0;
}
コンストラクタの存在しない構造体を宣言する際にconstを付けようとするとこの初期化方法が便利でよく使うんだけど、
この初期化方法ができる構造体の事をPOD(プレーンオールドデータ)と呼ぶ。
厳密な定義ではないけど、ようはコンストラクタやpublic,privateなどの指定が一切無いC言語で書かれたノーマルな構造体の事をPODと呼ぶと言ってしまったほうが説明が楽かな、当然正しい知識を知りたいなら別途調べて。
原因の詳細
んでやっとこ警告の説明に入れる。
ようはこのPOD型の構造体に対して配置newを呼び出すコードに対してコンパイラが警告を出しているという話。
そもそもの問題は5行目の次のコードが「コンストラクタの定義されないPOD型でコンストラクタを呼び出している」事にある。
AAA* pTest = new ( &test ) AAA();
実はこれを次のようにコンストラクタ呼び出しをやめるよう書き換えれば警告は出なくなる。
AAA* pTest = new ( &test ) AAA; // ()を外した.
じゃあこれで良いじゃんって思うかもしれないけど、例えばテンプレートでは指定された型がPODかどうか見てない場合次のようなコードになる。
T* p = new ( &t ) T();
これだとPOD型を渡した際に結局警告が出る。(is_podとかは本稿の主題とずれるのでスルーしてください)
んでそもそもこの警告は何を意味しているのかと言うと、
どうも昔と現在でPOD型の構造体を配置newで初期化し、更に上記のようにコンストラクタを呼び出した場合の初期化方法が違うらしい。
この警告は、POD (plain old data) オブジェクトを () で初期化する場合に、Visual Studio .NET に付属している Visual C++ コンパイラの動作変更を報告します。コンパイラは、既定でオブジェクトを初期化します。
ようは昔を知ってる人に対して動作が変わったって話なので、直せる場合は直しても良いけど基本的には無視安定。