C++クラスと特殊メンバー関数

最終更新: 2024-08-28 06:08:01 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

C++のクラスは、表現、不変条件、およびそれらを確立・維持する操作を定義します。コンストラクターはオブジェクトの不変条件を確立し、メンバー関数はそれを維持し、デストラクターはそのオブジェクトが所有するリソースを解放します。所有権ポリシーを最初に設計してください。これは、コンパイラーが生成する特殊メンバーが正しいかどうかを決定するためです。

構築と破棄 #

コンストラクターはクラス名を持ち、戻り値の型を持ちません。デフォルトコンストラクターは、パラメーターを持たないか、すべてのパラメーターにデフォルト値が指定されています。コピーコンストラクターとムーブコンストラクターは、同じクラスの左辺値または右辺値から新しいオブジェクトを初期化します。コピー代入とムーブ代入は既存のオブジェクトを更新します。

cpp
#include <string>
#include <utility>

class Connection {
public:
    explicit Connection(std::string endpoint) : endpoint_(std::move(endpoint)) {}

private:
    std::string endpoint_;
};

メンバーは、コンストラクターの初期化子リストに記述された順序では初期化されません。構築は次の順序で行われます。

  1. 最も派生したオブジェクトを構築する場合の仮想基底。
  2. 基底指定子の順序に従った直接基底。
  3. 宣言順に従った非静的データメンバー。
  4. コンストラクター本体。

したがって、初期化子は宣言順に記述し、後から宣言されたメンバーから別のメンバーを初期化しないでください。

cpp
struct Valid {
    int first;
    int second;

    Valid(int value) : first(value), second(first + 1) {}
};

破棄は、完了した構築順序を逆にたどります。通常の破棄は自動記憶域とRAII所有者に任せてください。明示的なデストラクター呼び出しは、ライフタイムと、その後の記憶域の再利用の両方が確立されている、制御された記憶域管理で使用します。

特殊メンバー関数とRule of Zero #

特殊メンバー関数は、デフォルトコンストラクター、コピーコンストラクター、ムーブコンストラクター、コピー代入演算子、ムーブ代入演算子、およびデストラクターです。実装は、クラスがユーザー宣言していない操作を暗黙的に宣言し、その操作が必要になった時点で定義します。宣言は、暗黙的に宣言することも、明示的にデフォルト化することも、明示的に削除することもできます。ユーザーが記述した定義を持つ関数は、ユーザー提供関数です。

次の抑制規則には、意図的な確認が必要です。

操作重要な規則
デフォルトコンストラクターユーザー宣言されたコンストラクターまたはコンストラクターテンプレートがあると抑制されます。
ムーブコンストラクターとムーブ代入ユーザー宣言されたコピーコンストラクター、コピー代入、ムーブ操作、またはデストラクターがあると、暗黙のムーブ操作は両方とも抑制されます。
コピーコンストラクターとコピー代入ユーザー宣言されていない場合は暗黙的に宣言されますが、基底またはメンバーが必要な操作を実行できないため、削除済みとして定義されることがあります。

参照メンバーと不適切なconstメンバーは、暗黙の代入を削除することがよくあります。いずれか一方のムーブ操作を宣言すると、暗黙的に宣言されたコピー操作は両方とも削除済みとして定義されます。基底またはメンバーにあるアクセス不能、削除済み、または使用できない操作は、他の暗黙の特殊メンバーを削除することがあります。基底とメンバーが標準の構造要件を満たす場合、コンパイラー生成関数はtrivialになることもあります。trivial性は型の性質であり、所有権ポリシーではありません。

Rule of Zeroを優先してください。正しい所有権動作を持つ型を組み合わせ、ドメイン型では特殊メンバーを宣言しないようにします。

cpp
#include <string>
#include <utility>
#include <vector>

class Document {
public:
    explicit Document(std::string title) : title_(std::move(title)) {}

private:
    std::string title_;
    std::vector<std::string> paragraphs_;
};

カスタムリソース所有者には、明示的で一貫したポリシーが必要です。ムーブ専用の所有者はコピーを削除し、正確に一度だけ解放し、転送が失敗しない場合はムーブをnoexceptにする必要があります。従来の「Rule of Three」の操作のいずれか、またはムーブ操作のいずれかを定義した場合は、5つの関数を自動的に実装するのではなく、すべてのコピー、ムーブ、破棄の動作を確認してください。

継承と仮想ディスパッチ #

public継承は代替可能性を表します。Baseに対して正しいコードは、Derivedに対しても正しく動作し続けなければなりません。「is-a」関係ではない実装の再利用にはコンポジションを使用します。private継承とprotected継承は継承されたアクセス可能性を変更しますが、publicな代替可能性を公開しません。基底のprivateメンバーは、派生クラスから名前でアクセスできないままです。

cpp
struct Shape {
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

struct Circle final : Shape {
    double area() const override;
};

最終オーバーライド関数を持たない純粋仮想関数があるクラスは抽象クラスになります。純粋仮想デストラクターにも定義が必要です。すべての派生デストラクターがそれを呼び出すためです。仮想ディスパッチは、オブジェクトの動的型に対する最終オーバーライド関数を選択します。ただし、構築中と破棄中は例外であり、呼び出しはコンストラクターまたはデストラクターのクラス階層内でのみディスパッチされます。基底クラスのコンストラクターまたはデストラクターから派生クラスの状態に依存する仮想呼び出しは避けてください。

意図したすべてのオーバーライドにoverrideを使用してください。シグネチャの不一致を拒否できるためです。基底ポインターを介して破棄される可能性がある多相基底には、publicな仮想デストラクターが必要です。そうでなければ、その破棄経路を禁止してください。

演算子とリテラルサフィックス #

演算子の既存の意味を維持する場合に限り、演算子をオーバーロードしてください。operator=とoperator->は非静的メンバーでなければなりません。C++23より前では、operator[]とoperator()も非静的である必要がありました。C++23では、この2つの演算子について静的オーバーロードが許可されています。前置インクリメントは慣例的にT&を返し、後置インクリメントは未使用のint識別子を受け取り、慣例的に以前のT値を返します。

cpp
class Flag {
public:
    explicit operator bool() const noexcept;
    Flag& operator++();   // prefix
    Flag operator++(int); // postfix
};

変換演算子は、暗黙変換がインターフェースの意図的な一部である場合を除き、explicitを付けてください。演算子は、優先順位、引数の数、または短絡評価の動作を変更できません。また、新しい演算子トークンを導入することもできません。

ユーザー定義リテラル演算子は名前空間スコープで宣言します。メンバー関数でリテラルサフィックスを定義することはできません。引数依存ルックアップでは、ユーザー定義リテラル演算子は見つかりません。サフィックスはアンダースコアで始めなければなりません。アンダースコアのないサフィックスは標準ライブラリ用に予約されています。また、アンダースコアの後に大文字が続く識別子や、2つのアンダースコアで始まる識別子は予約されています。

cpp
#include <cstddef>

struct Bytes {
    std::size_t value;
};

constexpr Bytes operator"" _bytes(unsigned long long value) {
    return {static_cast<std::size_t>(value)};
}

constexpr Bytes cache = 64_bytes;

調理済み整数リテラル演算子と浮動小数点リテラル演算子は、それぞれunsigned long longとlong doubleを受け取ります。文字列リテラル演算子は、対応する文字ポインターとstd::size_tの長さを受け取ります。文字リテラル形式では、該当する文字型を使用します。リテラルカテゴリで許可されているパラメーター形式のみを選択し、サフィックスの単位と変換規則を明示してください。

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)