Bo2SS

Bo2SS

1 クラスとオブジェクト

コース内容#

タイプと変数#

クラスとオブジェクトの別名と理解できる

  • image-20210723211429904
  • C++ ではタイプの最小ビット数のみが規定されているため、いくつかのコンパイラはより多くのビット数を実現できる

タイプ#

= タイプデータ + タイプ操作

  • 例えば:int=4 バイトサイズのデータ + 基本操作(+-*/%)、データ構造を連想できる、int、double タイプは本質的にデータ構造である
  • タイプデータ + タイプ操作 ➡️メンバー属性 + メンバーメソッド
    • C 言語の struct の強化版と理解できる(属性を持つことができるが、メソッドを持つことはできない)

アクセス権限

クラス内とクラス外の概念を区別する

アクセス権限はクラス内で設定され、クラス外がクラス内にアクセスできるかどうかを制御する

  • public:公共
    • クラス内とクラス外の両方からアクセスできる
  • private:プライベート
    • クラス内のメソッドのみがアクセスできる
  • protected:保護された
    • クラス内に加えて、継承されたクラス内でもアクセスできる
  • friendly:フレンド
    • 修飾された関数はクラス内のプライベートメンバーと保護されたメンバーにアクセスできる

コンストラクタとデストラクタ#

オブジェクトのライフサイクル:構築→使用→破棄

三種類の基本的なコンストラクタ#

ローカル変数の初期化を連想し、オブジェクトも初期化が必要

コンストラクタタイプ使用方法⚠️注意
デフォルトコンストラクタPeople bob;
プロトタイプ:People ();
1、引数なしのコンストラクタ
2、コンパイラが自動生成する
変換コンストラクタPeople bob("DoubleLLL");
プロトタイプ:People (string name);
1、1 つの引数を持つコンストラクタ
2、その引数はクラスのメンバー変数に渡され、かつ本クラスの const 参照ではない
[PS] 暗黙の型変換に似ている
コピーコンストラクタPeople bob(hug);
プロトタイプ:People (const People &a);
1、特殊な有引数コンストラクタで、本クラスのオブジェクトが渡される
2、代入演算子 "=" とは等価ではない
[PS] const & で処理する方が便利

デストラクタ#

オブジェクトを破棄する

プロトタイプ:~People ();

⚠️注意:

1、引数はなく、戻り値もない

2、リソース回収時に使用

小結#

どちらも戻り値がなく、関数名はクラス名と一致する

  • プロジェクト開発では、コンストラクタとデストラクタの機能は非常にシンプルに設計される
    • ❓なぜコンストラクタ内で大量のリソースを申請しないのか?
    • 理由:コンストラクタのバグは、コンパイラが検出しにくい
    • 解決方法:擬似コンストラクタ、擬似デストラクタ;ファクトリーデザインパターン
  • [+] ムーブコンストラクタ(別の重要なコンストラクタ、後で学ぶ)
    • C++ 11 標準から ——C++ が再び神壇に戻った標準
    • それ以前は、STL の性能が低下していた。C++ の言語特性が悪く、左値と右値の概念を区別していなかったため、STL の使用中に発生する大量のコピー操作、特に深いコピー操作が性能に大きく影響した
    • その後、右値の概念が導入され、ムーブコンストラクタが登場した

戻り値最適化(RVO)#

コンパイラはデフォルトで RVO を有効にしている

導入#

オブジェクトaはfun()を通じて戻り値を構築する

image-20210723211452133

出力結果:

image-20210723211500226
  • 理論的には:transform が 1 回、copy が 2 回出力されるべきである
    • 詳細は具体的な分析を参照👇
  • 実際には:copy は出力されず、a.x の値はオブジェクト temp の x 値であり、つまりローカル変数 temp のアドレスがオブジェクト a のアドレスを直接使用した(先に開かれたオブジェクト a のデータ領域、以下を参照)
    • temp はより参照に近い
    • コンパイラの最適化が存在し、戻り値最適化 RVO🌟

具体的な分析#

オブジェクト初期化プロセス:1、オブジェクトデータ領域を開く➡️2、コンストラクタをマッチさせる➡️3、構築を完了する

  • オブジェクト初期化プロセスを理解した後、上記のコード内の構築プロセスを分析する:
    • image-20210723211510540
    • まず、オブジェクト a のデータ領域を開く
    • 次に、func 関数に入ると、オブジェクト temp のデータ領域を開き、「変換コンストラクタ」を通じてローカル変数 temp を初期化する ——A temp (69);
    • さらに、「コピーコンストラクタ」を通じて temp を一時的な匿名オブジェクトにコピーし、オブジェクト temp を破棄する ——return temp;
    • 最後に、「コピーコンストラクタ」を通じて一時的な匿名オブジェクトを a にコピーし、一時的な匿名オブジェクトを破棄する ——Aa= func ();
    • 👉このように、プロセスには 1 回の変換コンストラクタと 2 回のコピーコンストラクタが含まれる
  • コンパイラの最適化
    • 第 1 回の最適化では、最初の「コピーコンストラクタ」をキャンセルし、直接 temp を a にコピーする(Windows での最適化)
    • 第 2 回の最適化では、2 回目の「コピーコンストラクタ」をキャンセルし、直接 temp が a を指す(Mac、Linux での最適化)

RVO を無効にした場合#

g++ -fno-elide-constructorsでソースファイルをコンパイルすることで、RVOを無効にできる

image-20210723211519691
  • 2 回の追加の「コピーコンストラクタ」が発生した、具体的な分析は上記を参照

注意点#

  • コンパイラは本質的に this ポインタを置き換えることで RVO を実現している
  • コンパイラは一般的にコピーコンストラクタを最適化するため、プロジェクト開発ではコピーコンストラクタの意味を変更しないこと
    • つまり:コピーコンストラクタ内ではコピー操作のみを行い、他の操作を行わないこと
    • 例えば:コピーコンストラクタ内でコピーされた属性に 1 を加えると、コピーコンストラクタの意味に合わず、コンパイラがコピーコンストラクタを最適化した後、結果は最適化前と一致しなくなる

+ 代入演算の最適化#

RVOも存在する——1回のコピーコンストラクタを最適化し、ローカル変数を一時的な匿名オブジェクトにコピーする

image-20210723211527733
  • 赤枠部分のコードを追加した
  • 最適化前の結果:
    • image-20210723211535203
    • 1 回の「変換コンストラクタ」 + 1 回の「コピーコンストラクタ」
  • 最適化後の結果:
    • image-20210723211540883
    • 1 回の「コピーコンストラクタ」

+ コピーコンストラクタの呼び出し分析#

さまざまな書き方で、コピーコンストラクタはどのように呼び出されるのか?

シナリオ:クラス A にカスタムクラス Data のオブジェクト d が含まれ、赤枠は追加されたコード

image-20210723211549347

「主に 29 行に注目;RVO コンパイルを無効にしないと、コピーコンストラクタをスキップする」

  1. カスタムコピーコンストラクタを定義し、各メンバー属性を明示的にコピーする。つまり、29 行は変更しない
  • image-20210723211557942
  • オブジェクト d を構築する際、Data クラスのコピーコンストラクタが呼び出される
  1. カスタムコピーコンストラクタを定義せず、各メンバー属性を明示的にコピーしない。つまり、",d (a.d)" を削除する
  • image-20210723211603337
  • オブジェクト d を構築する際、Data クラスのデフォルトコンストラクタが呼び出される(カスタム時に明示的にコピーしない場合、デフォルトコンストラクタがマッチする)
  1. コピーコンストラクタをカスタマイズせず、コンパイラが自動的にデフォルトのコピーコンストラクタを追加する。つまり、29〜31 行を削除する
  • image-20210723211610555
  • オブジェクト d を構築する際、Data クラスのコピーコンストラクタが呼び出される(コンパイラのデフォルト)

結論

❗️期待される結果を得るためには、カスタムコピーコンストラクタを定義する際に、各メンバー属性を明示的にコピーする必要がある

  • もしカスタムコピーコンストラクタが何も書かれていなければ、その関数は何も行わない

その他の知識点#

参照#

参照はそのバインドされたオブジェクトの別名である

  • ❗️参照は定義時に初期化する必要があり、バインドされたオブジェクトのように:
    • People a;
    • 定義時に初期化:People &b = a;
    • さもなければ:People &b; b = c; という曖昧さが生じる —— バインドされたオブジェクトか、代入か?

クラス属性とメソッド#

static キーワードが付加されている
メンバー属性(各オブジェクトに特有)やメンバーメソッド(this ポインタが現在のオブジェクトを指す)とは異なる

  • クラス属性:そのクラスのすべてのオブジェクトに共通の属性
    • グローバルにユニークで、共有される
    • 例えば:全人類の数 —— 人類中のすべてのオブジェクトの数
  • クラスメソッド:特定のオブジェクトに単独で属さないメソッド
    • オブジェクトにバインドされず、this ポインタにアクセスできない
    • 例えば:特定の高さが合法的な身長かどうかをテストする

const メソッド#

オブジェクトのメンバー属性を変更せず、非 const メンバー関数を呼び出すことはできない

  • const オブジェクトに使用される(その属性は変更できない)

⚠️:

mutable:可変であり、その修飾された変数は const メソッド内で変更可能

default と delete#

デフォルト関数の制御、C++ 11

  • default:コンパイラがデフォルトで提供するルールを明示的に使用する
    • 特殊メンバー関数(デフォルトコンストラクタ、デストラクタ、コピーコンストラクタ、コピー代入演算子)のみ適用され、かつその特殊メンバー関数にはデフォルト引数がない
    • ❗️この特性には機能的な意味はなく、C++ の設計哲学である:可読性、保守性などに注目する
      • この理念を理解することで、C++ における自分の美的基準を向上させることができる
  • delete:特定の関数を明示的に無効にする

struct と class#

  • struct
    • デフォルトのアクセス権限:public(ブラックリストメカニズム、private メンバーを明示的に定義する必要がある)
    • クラスを定義するためにも使用され、メンバー属性とメソッドを持ち、C 言語と区別する必要がある
  • class
    • デフォルトのアクセス権限:private(ホワイトリストメカニズム:public メンバーを明示的に定義する必要がある)

❓:C++ はなぜ struct キーワードを保持するのか?デフォルトの権限はなぜ public なのか?

  • すべては C 言語との互換性のためであり、普及の難易度を減少させるためである

PS:フロントエンド言語 JavaScript は、普及の難易度を減少させるために、名前を借りて Java の人気を利用し、本質的には Java とは無関係である

コードデモ#

クラスの例#

image-20210723211620159
  • クラス内の属性とメソッドの宣言と定義は分けることをお勧めする
  • this ポインタはメンバーメソッド内でのみ使用され、現在のオブジェクトのアドレスを指す

cout の簡単な実装#

image-20210723211627367
  • cout はオブジェクトであり、高度な変数である
    • 自身の参照を返すことで連続的な cout を実現でき、参照を使用する理由は後で理解する
    • 文字列は const 型の変数で受け取る必要があり、そうでないと警告が出る。なぜなら、文字列はリテラルだからである
  • 名前空間の本質:同じオブジェクト名が異なる名前空間に存在できる

コンストラクタとデストラクタ#

image-20210723211634762 image-20210723211642463

実行結果:

image-20210723211648649

1)デストラクタの順序についての考察#

  • 画像
  • 構築順序:オブジェクト a、オブジェクト b
  • 破棄順序:オブジェクト b、オブジェクト a
  • ❓なぜデストラクタの呼び出し順序が逆になるのか?それはコンパイラが生成した特例なのか、それとも正常な言語特性なのか?👉言語特性
    • オブジェクト b の構築はオブジェクト a の情報に依存する可能性がある➡️デストラクタの時にオブジェクト b もオブジェクト a の情報を使用する可能性がある➡️オブジェクト b はオブジェクト a より先に破棄される必要がある
    • ❗️誰が先に構築されるか、それが後に破棄される
    • PS
      • これはオブジェクトがヒープ領域にあるかスタック領域にあるかに関係なく、実験によれば、破棄順序は常に逆である
      • トポロジー順序の一種と見なすことができる

2)変換コンストラクタ#

  • ❓なぜ変換コンストラクタ(単一引数のコンストラクタ)と呼ばれるのか?
    • 変数をそのタイプのオブジェクトに変換する
  • 🌟a=123 は演算子オーバーロードを含む:暗黙の型変換➡️代入➡️破棄、詳細はコードを参照

3)コピーコンストラクタ#

1、参照を追加する:無限にコピーコンストラクタを呼び出すのを防ぐ

  • ❗️もしコピーコンストラクタが:A (A a) {} の場合、A b = a; の時、
  • [形参 a] が参照でない(値渡し)ため、[オブジェクト a] を [形参 a] にコピーするために一時オブジェクトを生成する必要がある
  • この時、再びコピーコンストラクタが呼び出され、このコピーコンストラクタも同様のプロセスを経る
  • したがって無限再帰が発生する
  • PS:参照は何のコピー行為も生じず、ポインタよりも便利である

2、const を追加する:const タイプのオブジェクトが非 const タイプのコピーコンストラクタを使用するのを防ぎ、エラーを報告する

  • コピーされるオブジェクトが変更されるのを防ぐ

注:

  • オブジェクトを定義する際、"=" はコピーコンストラクタを呼び出し、代入演算ではない。例えば、A b = a

4)考察#

  • オブジェクトはいつ構築が完了するのか?
    • 「コードを参照し、デフォルトコンストラクタの例を参照」
    • 機能的な構築 [論理的に]:46 行目に到達した時、コンストラクタは表面的に実行が完了した
    • コンパイラの構築[実際には]:39 行目に到達した時、すでにオブジェクトメンバーを呼び出すことができる❗️🌟
  • 考察部分のコードを通じて理解できる:
    • シナリオ:クラス Data に有引数コンストラクタを追加し、コンパイラが自動的に追加したデフォルトコンストラクタを削除する
    • プロセス:クラス A オブジェクトを生成する際、メンバー属性 Data クラスオブジェクト p、q はすでに構築されている必要があり、この時 Data クラスのデフォルトコンストラクタはすでに削除されている
    • 結果
      • 初期化リストを使用して p、q を初期化しない場合、エラーが発生する
      • 初期化リストを使用すれば、成功する。初期化リストはコンパイラが言うところの構築に属する
    • ❗️これはコンパイラの構築が関数宣言後(39 行目)に完了することを示している
  • ⚠️:
    • コンパイラはデフォルトコンストラクタとコピーコンストラクタを自動的に追加する
    • 構築行為はすべてコンパイラが言うところの構築に置くべきであり、初期化リストを使用すること

+)左値参照#

  • 後で学ぶ

+)フレンド関数#

  • クラス内で宣言する(同時にクラスの管理者の承認を保証する)
  • クラス外で実装する。本質的にはクラス外の関数だが、クラス内のプライベートメンバー属性にアクセスできる

深いコピー、浅いコピー#

コピーオブジェクト:配列

image-20210723211710169
  • コンパイラがデフォルトで追加するコピーコンストラクタは浅いコピーである
    • ポインタの場合、アドレスのみをコピーするため、コピー後のオブジェクトの変更は元のオブジェクトを変更する
  • カスタム深いコピー版のコピーコンストラクタ
    • ポインタの場合、その指すアドレスの値をコピーする
    • PS:コンストラクタは初期化時にのみ呼び出され、自分自身をコピーする行為は存在しない

⚠️:

  • オーバーロードされた "<<" の const パラメータに適応するため、const 版の "[]" オーバーロードを実装する必要がある
  • Array クラスの end と配列の終端データは関係がなく、配列の越境状況を監視するために使用される

new、malloc の違い分析#

image-20210723211717190

実行結果:

image-20210723211724784
  • malloc と new はどちらもスペースを申請でき、それぞれ free と delete(配列の場合は delete [])でスペースを破棄する
  • new は自動的にコンストラクタを呼び出し、対応する delete は自動的にデストラクタを呼び出す。一方、malloc と free はどちらも呼び出さない
  • malloc + new は原地構築を実現でき、深いコピーによく使用され、その際、new は異なるクラスのコンストラクタに対応できる

クラス属性とメソッド、const、mutable#

image-20210723211734657
  • クラス属性:クラス内で static を付けて宣言し、クラス外で初期化しない
  • クラスメソッド:オブジェクトまたはクラス名の 2 つの方法で呼び出すことができる
    • したがって、オブジェクトが呼び出すメソッドは必ずしもメンバーメソッドであるとは限らず、クラスメソッドである可能性もある
  • const:const オブジェクトは const メソッドのみを呼び出すことができ、const メソッド内では const メソッドのみを呼び出すことができる
  • mutable:その修飾された変数は const メソッド内で変更可能

default、delete#

機能要件:特定のクラスのオブジェクトがコピーできないようにする

image-20210723211742795
  • コピーコンストラクタと代入演算子を無効にする
    • コピーコンストラクタ:=delete に設定するか、private 権限に置く
    • 代入演算子オーバーロード関数:同様に = delete に設定する(決して代入演算を使用できない)、または private 権限に置く(クラス内メソッドのみが代入演算を使用できる)
    • PS:代入演算子は const 版と非 const 版の両方を考慮する必要がある
  • ❓オブジェクトが本当にコピーできないようにするには、コピーコンストラクタと代入演算子オーバーロードの両方を = delete に設定する必要がある。そうでなければ、クラス内メソッドでもそのオブジェクトをコピーできる

追加知識点#

  • コンストラクタの後の波括弧
    • 波括弧を追加すると、関数の実装を示す
    • 波括弧を追加しないと、単なる関数宣言であり、専用の実装を書く必要がある

考察点#

  • コンパイラがデフォルトで行う多くのことに注目することが重要であり、これが C++ の複雑な部分である

ヒント#

  • C++
    • 学習方法:プログラミングパラダイムに従って分類して学ぶ
    • 学習の重点:プログラムの処理フロー [C よりもはるかに複雑]

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。