C++ Language and Library Notes

最終更新: 2025-12-01 21:05:41 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

この小規模なアンソロジーは、独立した記事を必要としない言語およびツールチェーン向けの規則をまとめます。 見た目が似た C++ の記法でも所有権、検索、ライフタイムへの影響が異なるため、各セクションでは機能を支配する境界を示します。

リソース所有権と任意状態 #

所有権とは、適切な時点でリソースを解放する責任です。RAII でモデル化し、構築時に取得し、破棄時に解放し、 例外的な制御フローをまたいでもオブジェクトの不変条件を維持します。std::unique_ptr や std::vector などのメンバーがすでにリソースポリシーを実装している場合は Rule of Zero を優先します。 raw リソースを直接管理する型は、一貫したデストラクター、コピー方針、ムーブ方針、失敗方針を定義する必要があります。

std::make_unique<T>(args...) expresses sole ownership without exposing a standalone new; it has been available since C++14. std::make_shared<T>(args...) may combine the control block and object allocation. Combined allocation is not always appropriate: a weak_ptr can keep that allocation alive after the object is destroyed, and custom deletion or an existing raw pointer can require direct construction. When direct construction is necessary, establish the smart-pointer owner in the same full expression. Before C++17 tightened function-argument evaluation sequencing, separating allocation from ownership inside a larger call could also leak if another argument threw.

任意のサブオブジェクトの表現は、その所有権と同一性から選択します。

  • 有効な空状態を持つ直接メンバーには間接参照がありませんが、その状態は明確でなければなりません。
  • std::optional<T> は存在しない可能性のある T を包含オブジェクトに格納し、emplace は別の割り当てなしで包含オブジェクトのライフタイムを開始します。
  • std::unique_ptr<T> は独立したオブジェクトの排他的所有権を表し、不完全型または多相型をサポートしますが、間接参照と nullable ハンドルのコストがあります。

Neither optional nor heap allocation lets an object outlive its owner. A reference, pointer, or std::reference_wrapper<T> is a non-owning handle; the referred object must outlive every use. std::ref and std::cref are useful when a value-oriented interface, such as a container or std::bind, must carry a reference.

インターフェース、演算子、オブジェクトモデル #

対称な二項演算子は、両方のオペランドで変換を検討できるよう、非メンバーにすることがよくあります。 friend として宣言すると限定された private アクセスを与えますが、friend 関係によってメンバーになるわけではありません。

cpp
class Quantity {
public:
    explicit Quantity(int value) : value_{value} {}
    friend Quantity operator+(Quantity left, const Quantity& right) {
        left.value_ += right.value_;
        return left;
    }
private:
    int value_;
};

多相クラスには virtual 関数があります。標準は仮想ディスパッチの動作を規定しますが、vptr、vtable、そのレイアウト、ABI は規定しません。 多くの実装は隠しポインターと関数ポインターのテーブルを使いますが、多重継承とコンパイラーオプションによってそのモデルへの仮定は無効になります。 アプリケーションコードは表現の詳細ではなく virtual 関数に依存します。

An abstract class is complete once its class definition is complete, even though a pure virtual function prevents direct instantiation. An incomplete class has only been declared and cannot be used for sizeof or direct data members. In a non-virtual diamond, a derived object contains two base subobjects, so an unqualified inherited member can be ambiguous. A static data member belongs to its declaring class, however; it is not duplicated for each base subobject.

cpp
struct A {
    int value;
    static int count;
};
struct B : A {};
struct C : A {};
struct D : B, C {};

int A::count = 0;
D object;
// object.value is ambiguous: object contains two A base subobjects.
D::count = 1; // One static member; no A object is selected.

式、参照、解析 #

すべての非 void 式は prvalue、xvalue、lvalue のいずれかです。lvalue と xvalue はエンティティを識別するため glvalue であり、 prvalue と xvalue は rvalue です。T&& と宣言された変数を含め、名前付き変数は lvalue 式です。 スカラー prvalue には組み込みアドレス演算子で取得できる同一性がありません。

参照はオブジェクトではありません。名前付き参照は参照先の型の lvalue 式です。 テンプレート置換で参照への参照を形成できます。参照折りたたみは T& &、T& &&、T&& & を T& にし、 T&& && だけが T&& のまま残ります。これらの規則により、入れ子の参照オブジェクトを作らずに転送できます。

const member functions constrain the implicit object parameter. A const overload can run on const and non-const objects; a non-const overload cannot run on a const object. Within a const member, non-mutable members cannot be modified through *this.

テキストを宣言として解析できる場合、C++ は宣言として解析します。この most-vexing parse はオブジェクトを構築しません。

cpp
Widget widget(Factory()); // May declare a function.
Widget object{Factory{}}; // Unambiguously constructs an object.

整数変換とシフト #

整数昇格は、int がすべての値を表せる場合、bool、文字型、short、その他 rank が int より低い適格型を int に変換し、 そうでなければ unsigned int に昇格します。その後、通常の算術変換が共通型を選択します。 したがって、int がすべての unsigned char 値を表せる一般的なシステムでは、unsigned char 値の算術は int 算術として実行されます。

cpp
unsigned char left = 42;
unsigned char right = 55;
int sum = left + right;

シフトでは両方のオペランドが整数昇格を受けます。負のカウント、または昇格後の左オペランドの幅以上のカウントは未定義動作です。 移植可能なビット操作には符号なし型を使い、カウントを検証します。C++ に Java 形式の >>> はなく、符号なし値の右シフトは 0 ビットを挿入します。 C++20 は符号付き右シフトを算術右シフトとして規定しますが、表現が重要なビット指向コードでは符号付きオペランドを避けます。

callable、検索、多相性 #

各ラムダ式には固有の名前なしクロージャ型があり、decltype で特定のクロージャオブジェクトの型を指定できます。 キャプチャはクロージャオブジェクトに保存されるため、キャプチャした参照はライフタイムを延長しません。 ジェネリックラムダにはテンプレート化された呼び出し演算子があり、使用された引数型に対して動作をインスタンス化します。

An unqualified function call combines ordinary lookup with argument-dependent lookup (ADL). ADL considers namespaces and classes associated with its argument types, which supports customization patterns such as:

cpp
using std::swap;
swap(left, right); // Standard fallback plus an associated-namespace overload.

Qualified calls suppress ADL. Do not rely on ADL for fundamental types: an int does not associate a call with an unrelated namespace.

Function parameters declared with a function type adjust to pointers to function type. Function names also convert to function pointers in the applicable contexts.

c
void callback(void);
void invoke(void f(void));
void invoke_pointer(void (*f)(void));

void use(void) {
    invoke(callback);
    invoke_pointer(&callback);
}

C++ adds overload sets, templates, and member-function pointers, so a function name can need a target type before one overload is selected.

Ad hoc polymorphism selects an overload from static argument types. Parametric polymorphism instantiates templates from compile-time requirements. Subtype polymorphism dispatches a virtual call through a base pointer or reference at runtime. Type erasure—such as std::function, a concept/model wrapper, or a base interface—replaces a concrete type in a boundary with a runtime interface. It can reduce header and ABI coupling, but may add indirection, allocation, wrapper storage, and runtime checks.

遅延初期化と並行性 #

関数ローカル static の初期化はスレッドセーフです。複数スレッドが同時に宣言へ入ると、いずれかが進む前に初期化が一度完了し、 例外が発生した場合は後で再試行できます。スレッド間で共有するメンバーを遅延初期化するには、 std::mutex、std::call_once、または適切な atomic プロトコルで状態遷移を保護します。

cpp
const Settings& settings() {
    static const Settings value = load_settings();
    return value;
}

volatile はアクセスをアトミックにせず、happens-before 関係も作りません。mutex、条件変数、std::atomic の代わりにはならず、 キャッシュフラッシュやメインメモリトラフィックも保証しません。メモリマップド I/O など、プラットフォーム契約が観測可能な volatile アクセスを要求する場合だけ使います。

翻訳単位の依存関係 #

ヘッダーを include すると、そのヘッダーの宣言変更でクライアントの再コンパイルが必要になる場合、コンパイル時依存関係が作られます。 公開インターフェースで具体的なコンテナまたはそのイテレーター型を公開すると、クライアントがその表現に結合されます。 狭い抽象インターフェースまたは Pimpl 境界は実装の自由度を保てますが、それぞれインターフェースまたは間接参照のコストが加わります。

関数内で構築される自動オブジェクトは、呼び出しごとに構築・破棄されます。 構築を避けるためだけにループの外へ移動するとライフタイムが変わり、意図したスコープを超えて状態、リソース、同期を保持する可能性があります。

コメント

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