teppynewは引数でサイズを受けますが、標準のnewはsizeが0でも正しく動作することを規格で定めています。自分で実装する際には意識するとちょっとだけ規格に準じたコードになってよいです。teppy「空のクラス」を定義します。classという意味ではありませんよ。classという意味です。メンバ変数を持たず継承もしていない、とにかくデータとしての要素の無いクラスです。EmptyClass{};このEmptyClassのサイズはいくつでしょうか?Emptyという位なのでゼロなのかと思いきや、sizeofで計るとゼロ以外の数値になります。Wandboxで見てみましょう。?その答えはある名曲に隠されています。言葉はいつも奥の方から後ろに虚しさ連れて教えてくれたけれどこんなにもからっぽになったのに僕は歩き出した__ゆず「からっぽ」からっぽに見えても、みんな何かを抱えて生きているのです。ゼロなんてありえないんですね。C++のクラスも同じなんですね。納得感を得たところで、改めて言うと、C++の型は全くデータメンバが無い状態でも単独でインスタンスが作られる場合必ずサイズを持ちます。C++のテクニカルな理由で、「空のクラス」でも、「独立したオブジェクト」のサイズは0にできないのです。「テクニカルな理由」というのは、インスタンス化された際にオブジェクトのアドレスをとれるようにする為です。サイズ0ではアドレスが決まりませんからね。このようにc++では単独でインスタンス化されるオブジェクトは例外なくサイズを持ちます。メンバ変数の一部として定義された場合もサイズを持ちます。e;};この場合でも&Hoge::eを決めなければいけないのでeはサイズを持ちます。1byteとはいえ無駄なメモリを消費することになります。コピー禁止をmix-inするboost::noncpyable等はまさにメンバを持たない空のクラスですね。コピー禁止するたびにデータサイズが増加したらたまったものではありません。そんなわけで、規格では、空の型が別の型を継承していて、継承元の型が既にサイズを持っている場合や、逆に空の型の継承先がサイズを持っている場合はEmptyClass自体の追加のサイズは無くしても良いことになっています。Optimization)と言います。EBOの効かないコンパイラなんてほとんどありませんが、場合によっては型サイズの肥大化やアライメントのズレの原因になるので注意する必要があります。EBOを意識した実装コンパイラが空のクラスを最適化してくれることは理解しました。プログラマもEBOが効くコードを意識して書きましょう。先のboost::noncopyableも空のクラスでしたが、他にも空のクラスを定義するシチュエーションは数多くあります。ファンクタなんかも、往々にしてoperator()だけ定義して、メンバ変数も特に無いというケースがあるかと思います。スマートポインタのデリータやコンテナのアロケータを渡して実装を決めるテンプレートクラスもEBOを意識した設計にすることでメモリ効率が上がります。以下が一例になります。オリジナルのコンテナクラスにテンプレートパラメータとしてアロケータを渡す実装で考えてみましょう。それを示すにはこの余白は狭すぎる。よってallocateメソッド以外のあらゆる定義を省くことにした。それを示すにはこの余白は狭すぎる。よってallocateメソッド以外のあらゆる定義を省くことにした。};まずはEBOを意識しない設計。以下コンテナとしての必要条件を満たす色々な機能の定義が続く...};普通にAllocatorをメンバとして持っています。続いてEBOを意識した設計。Allocatorを継承した設計になっています。以下コンテナとしての必要条件を満たす色々な機能の定義が続く...};サイズを計ってみましょう。16このようにちょっと意識することでメモリ効率の良いクラス設計ができるようになります。teppy1件のコメント名前を付けるという行為には責任が伴います。子供にせよ、ペットにせよ、変数名にせよ。ドラクエ5で子供の名前を付ける時に、かなり悩んだ経験を持つ人も多いのではないでしょうか。C++でコーディングしている時も名前を付ける機会は多いです。マクロ名にクラス名......他にもとにかく名前を付けなければいけないタイミングは多いです。その都度、ドラクエ5の時くらい悩んでいてはプロジェクトは全く進まなくなってしまうので、直感と経験でサクサク名前を付けましょう。ちなみにここで言及している「名前」のことをC++的に気取っていうと「識別子(Identifiers)」といいます。この識別子には色々ルールがあって、ルールに違反した識別子は使ってはいけません。識別子のルールに違反した場合、コンパイラがエラーにしてくれるものがあります。inlineはキーワードなのでエラー}一部演算子の代替表現?なんだそれは!?C++ではキーワードではないが、ある種の演算子と同意語の単語がいくつか用意されており、それらも識別子として使用できません。演算子の代替表現なのでエラー}これらのルールはコンパイルの段階でエラーになるので、詳細を覚えててなくても大丈夫です。以下のルールはエラーにはなりませんが、C++の予約語のため、使ってはいけません。0;}これらのルールに違反した識別子を使った場合の動作保証は無いので、絶対に使わないようにしましょう。__Japan_987654321_wwww___ssl93jjasf__なんて識別子が被るなんてあるわけないから別に問題ないじゃん!と思ったとしても、避けられるリスクは避けるべきです。言語の規格で使うべからずとされているものは使わない方がいいのです。インクルードガードを書く時にこのルールを犯しているコードをよく見かけます。_HOGE_H__コンパイラがエラーにしてくれないので、書く側で気をつけましょう。ディレクティブは、プリプロセスで解釈され、書かれたファイル名の内容を単にその場所に展開したものとして処理されます。boost.hppの内容が単に展開される一般的にファイルの先頭付近にinclude文はまとめて書く感じですが、コードの中に埋め込むことも可能です。以下のような、動物の名前を列挙しただけのファイル(animals.csv)があるとします。"kangaroo"};と展開されて、普通にコンパイルが通ります。このように#includeディレクティブはとにかく機械的に展開するものだ、という話です。普通に同じヘッダーファイルを複数includeしてしまうと、(機械的に展開するだけなので)includeの数だけその場所に展開されます。include元のファイルに何かシンボルの定義みたいなものが含まれている場合、多重定義エラーになります。0;}なので、一つのコンパイル単位中に同じファイルが複数回、展開されないようにする必要があります。多重に展開されることを防止するには、展開元のファイルに多重展開防止用のマクロ(インクルードガード)を仕込むのが定石とされています。c/c++が標準で提供する全てのヘッダーファイルにはインクルードガードが使われています。INCLUDE_GUARD_HOGE_HPPシンボルの定義名(例ではINCLUDE_GUARD_HOGE_HPP)は、一意に定義される文字列なら別に何でも良いです。複数includeされても、最初の一回以外はINCLUDE_GUARD_HOGE_HPPが定義済みなので単にスキップされますので、include元のファイル内に書かれたクラス定義などが多重定義されてしまう心配はありません。-#endifで囲うインクルードガードを、厳密には「内部インクルードガード」といいます。-#endifの部分をinclude先にも書く手法を「冗長インクルードガード」といいます。INCLUDE_GUARD_HOGE_HPPこのように書くと#include文の評価をする前に、スキップするか決められる為、コンパイル時間の短縮につながる場合があります。困ったことに、コンパイル時に複数のヘッダーファイルが何度も検出されるような状況下では、ヘッダーファイルを開き、#ifndefを評価し、終わりの#endifを走査するプロセスに時間がかかる可能性があります。場合によっては冗長インクルードガードを設定することにより、コンパイル時間を大幅に短縮できます。#62:インクルードガード冗長インクルードガードについては毎回外部に書く手間の割に大したメリットも無いのであまり使われていません。ガードにつかうマクロ名を内部インクルードガードと冗長インクルードガードで合わせなければいけない為、別々のファイルなのに結合が生じてしまいます。内部インクルードガードのマクロ名を変えた場合、冗長インクルード側のマクロ名も忘れずに変えなければなりません。外部の#includeガードは冗長で、現在のコンパイラ上では時代遅れである。インクルードする側とヘッダーファイルがガード名について合意していなければならない。これにより、密接な結合が生じ、脆弱となる。Standards今の時代には不要となった冗長インクルードガードですが、教養として押さえておくと良いでしょう。コモンセンスってやつですね。once言語機能ではありませんが、多くのコンパイラではこの魔法の言葉をファイル中に書いておくだけで複数インクルードされない仕組みを提供しています。インクルードガードを書くより、遥かにタイプ数が少なく済むので単純に楽です。onceによる複数include回避の方法はコンパイラに依存するため、コンパイラによっては使えないこともありますし、コンパイラの実装によっては予期せぬ不具合の原因になる場合があります。あるファイルを複数includeしない為に、そのファイルが既にincludeされたことがあるかをコンパイラは覚えておく必要があります。include済みのファイルかどうかを判定するために、ファイル単位に一意のシンボル名を定義するとします。その一意の文字列生成の為にファイルパスを埋め込んでシンボル名を作るような実装になっていた場合、すごーーーく長いパス(c:/user/xxxxxxxxxxxx/development/my_project/tekitou_na_project_name/game/lib/include/group/category/tekitou_na_header_file_name.hpp)の場合、途中でバッサリとパスが切られて、まったく同じシンボル名になって、初めてincludeしたファイルなのにスキップされるという、なかなか原因解明が困難な不具合に襲われる......といったケースがあるかもしれません。......はい、体験談です。言語標準じゃない機能なので私は使わなかったのですが、あの一件以来、さらに避けるようになりました。コンパイラによる拡張機能で、言語の機能ではないので、これをコード中に書いた瞬間に、そのコードはコンパイラ依存のコードになります。ポータビリティの高いコードを書きたい場合は、単に使わない方が得策です。onceを使わなくても、マクロによるインクルードガードで、コンパイラ依存の無い形で実現出来ますからね。onceも冗長インクルードガードと同様、includeするファイルの中を見に行く前に、以前にincludeしたことがあるかを判定出来るため、若干コンパイル時間が短くなることが期待できます。初学者はC++の勉強の一環として、CryENGINEのソースコードを読んだりすると思いますが、その中でもこのインクルードガードに触れることができます。以下はCryENGINEのあるヘッダの冒頭部分です。once#endifインクルードガードのマクロ名が__で始まっていますが、連続するアンダーバーを含む名前はC++の予約語なので真似するのは絶対に止めましょう。参照:禁忌の識別子スーパープログラマ集団でも平気でC++の規格に違反してしまうところを垣間見て勇気を得た所で先に進みます。onceしているのが分かると思います。onceによって、ちょっとでもコンパイル時間を短縮しようというという貪欲な姿勢が見てとれますね。このようにインクルードガードとの合わせ技で攻めるというアプローチもあるので覚えておくと良いかもしれません。Data)とは、C言語のデータと互換を持つデータ構造のことです。memcpyでデータをコピーできたりするものですが、それは本質的ではなく、とにかくPODの一番の意味はC言語のデータとbitレベルで完全に互換を持つということです。C++とC言語をまたぐプログラムがある場合、受け渡されるデータはPODでなければいけません。会話中での出現例でいうと「あれ、ちゃんとPODで作っといてよ!」という具合に使います。PODとは具体的にはどういうデータ構造でしょうか。C互換データ=PODは、複雑なクラスレイアウトや、構築、コピー、ムーブなどのユーザ定義のセマンティクスを配慮することなく、”単なるデータ”として利用できるオブジェクトである。type)であるこの2つの定義を満たせば、それは紛れもないPOD型です。簡単ですね!それぞれの定義をさらに掘り下げます。標準レイアウト型virtual関数を持たないvirtual継承をしていない参照メンバを持たない非staticメンバに対して複数のアクセス指定子を持たない(重要!)標準レイアウトでない型を継承していない標準レイアウトでない非staticメンバを持たない継承していてもいいが非staticメンバを持つクラスは継承ツリーの中で1つだけしかない4番目の項目に注目してください。クラスの不定なメモリレイアウトでも触れたとおり、複数のアクセス指定子でメンバを定義してある場合、標準レイアウトの保証はなくなります。トリビアル型トリビアルなデフォルトコンストラクタを持つトリビアルなコピー動作およびムーブ動作をする散々登場しているこの「トリビアルな○○」ってなんやねん!と、あなたは言うでしょう。トリビアルなデフォルトコンストラクタを持つ、トリビアルなコピー動作、ムーブ動作をするとは、大雑把に言うとコンパイラ定義のこれらの動作をするということです。コピーコンストラクタをユーザーが定義したら、それはもうトリビアルな型ではありません。引数無しコンストラクタをユーザーが定義したら、コンパイラ定義のデフォルトコンストラクタが無くなるのでこれもトリビアルな型ではありません。さあ、これで自分の書いたクラスがPODかどうか判断できるようになりました。こんなことは忘れてもいいのです。あるクラスがPODかどうか、本当に知りたいのならstd::is_podを使いましょう。std::endl;}}(コンパイラベンダーの実装にバグが無い限り)正確にPODの判定をしてくれます。小難しいことぁいいんだよ!という人へ。float...etc)しかメンバに持ってないとりあえず、これを抑えておけば大概はPODです。それでもPODじゃないデータ構造を定義してしまったら、逆にすごくね?と思うようにすると良いでしょう。blue_;};これもPOD。v;}};あと、おまけですがPODじゃない型のstaticメンバを持っていてもそのクラスのPOD性には影響ありません。teppy先人たちが書いたコードからは学びが多いものです。みんな大好きboostのnoncopyableのソースコードを読んでみましょう。boost8行目のコメントが興味深いですね。ADL(思いがけないADLの防止)どういう意味でしょうか。ご存知のとおり、ADLでは予期せぬネームスペースまで検索の対象になることがあり、時として不幸な不具合の温床になっていまうことがあります。それというのも、ADLによって、検索されるネームスペースに実引数の名前空間が暗黙に含まれる為なのですが、このboostのコードではその予防策が講じられているというわけです。noncopyable;とあります。この一行のtypedefにより、本来boost::noncpyable_::noncopyableというアクセスをboost::noncopyableと書くことができるようになります。実際のnoncopyableはnoncopyable_というネームスペースに存在している為、ADLによってboostネームスペース全てが検索候補になってしまうことを防いでいるのです。Firewallという手法です。ADL対策になりませんので気をつけて下さい。ADL対策にはなりません!大事なことなので2回言いました。Firewallという言葉は某コンソールゲーム機のSDKを読んでいて初めて知ったのですが、それ以外の場所で滅多にお目にかからない言葉なので、あまりメジャーな呼び方では無いのでしょう。Firewallと呼ぶようになりました。teppy大量のデータメンバを持つ大規模なクラスLargeObjectがあるとします。大量のデータメンバ};そして、LargeObjectをメンバに持つクラスがあるとします。大量のデータメンバを持つクラスをメンバに持つ};このObjectHolderクラスを扱うユーザーが、LargeObjectにアクセスしようとした時にちょっとしたミスを犯します。受けなくちゃいけないのに、&を書き忘れてしまいました。このコードを書いたプログラマはそのミスに気づいていません。コンパイルエラーになることもなく、実行時に粛々とLargeObjectのコピーが発生するだけです。アクセサ経由で受けたobjに操作しても結果はholder内に反映されません。これはObjectHolderを書いたプログラマも本意ではありません。想定してない操作ができてしまうことが問題です。クラスを書くということは、どういう操作を期待しているか、想定しているかをクラス自身に語らせる必要があります。不測のデータコピーを抑止する一つの方法としてアクセサの戻り値は参照ではなく基本的にポインタで返すというプラクティスがあります。別の方法としてLargeObject自身をコピー禁止にするというのも手です。クラスのコピーコンストラクタとコピー代入演算子をアクセス不能にすることでインスタンスのコピーを禁止することができます。member_[2048];};C++11より前の時代は=delete;による書き方が無いので、該当の関数をprivateにすることでコピー禁止を実現していました。これで、先ほどの&忘れる事件もコンパイルエラーになり、不測のコピーが発生してもすぐに気づくことができます。あるクラスのコピーコンストラクタ、代入演算子をアクセス不可にするとコピーは禁止できますが、第三者がそのコードを読んだ時に意図が分かりづらいという問題があります。このコピー禁止の仕掛け部分を専用のクラスに分離した上で継承することで各クラスにコピー禁止属性を持たせるというアプローチが一般的です。member_[2048];};これでLargeObjectがコピー禁止になり、さらにクラスの定義を見ただけでコピー禁止だということが分かりやすくなりました。C++でも説明されいます。コンパイラが生成するコピーコンストラクタとコピー代入演算子は、その基底クラスに対応する関数(同じ関数)を呼び出そうとします。対応する関数を規定クラスでprivateにしておけば、その呼び出しはエラーになるわけです。第3版またboost::noncopyableにも同様の実装があります。Idioms/コピー禁止ミックスインでも紹介されています。これらの情報も併せて参照すると良いでしょう。Lookup)というものがあります。和訳すると「実引数依存の名前検索」となり、読んで字の如く、「実引数」に依存した「名前検索」となります。ADLの挙動コードを見ながら順を追って理解してみましょう。0;}funcという関数が呼ばれるコードですが、これをただコンパイルするとエラーになります。......funcという関数は見る限りどこにも定義されていないからです!!............。0;}今度はコンパイルエラーにならずに無事、func関数が呼び出せます。当たり前すぎて、今、自分が何を読んでいるのか分からなくなってきたのではないでしょうか。明日はどっちでしょうか。