Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/Guide/TYPES-AND-CLASSES.md
diff options
context:
space:
mode:
authorLeitwolf112026-10-18 20:00:39 +0300
committerLeitwolf112026-10-18 20:00:39 +0300
commit8d1fb4807edc7920fbecd0554c9c61625827e19c (patch)
treedd3d7e1d4f48ca27140a2bd5f970817ad02ae35a /Guide/TYPES-AND-CLASSES.md
downloadprogmasoft-cpp-8d1fb4807edc7920fbecd0554c9c61625827e19c.tar.gz
Write the first revision of Progmasoft C++ Style
Progmasoft C++ Style is a style guide for C++20, written for anyone who wants a complete rule set to adopt. It writes down the conventions the Progmasoft C++ projects already follow (naming, the clang-format and clang-tidy profiles, exception-free error handling, Doxygen) and decides what they left open. Thirteen chapters under Guide/: 1. Principles: what the guide optimizes for and how to decide a case it does not cover. 2. Formatting: the full .clang-format profile, the reason for each choice, and the rules the tool cannot apply. 3. Naming: casing for every kind of name, acronyms, choosing words, namespaces, files. 4. Files and headers: extensions, #pragma once, includes, what goes in a header, the order inside a class, file size, vendored code. 5. Language: functions, auto, initialization, const and constexpr, casts, integers, strings, enumerations, templates and concepts, lambdas, control flow, and the features not used. 6. Types and classes: struct or class, final, construction, the rule of zero, inheritance, operators, strong types, optional and variant. 7. Errors: no exceptions by default, Expected and Error, invariants, fatal defects, destructors, diagnostics. 8. Ownership and memory: owners and borrowers, views and lifetimes, RAII, containers and deterministic iteration, allocation, undefined behavior. 9. Comments and documentation: Doxygen for public declarations, comments that explain why, TODOs that name an issue. 10. C interfaces: C11 headers, prefixes, opaque types, ownership and failure across the boundary, stability. 11. Concurrency: sharing less, threads, locks, thread-safety annotations, atomics, statics. 12. Testing: unit tests, fuzzing, sanitizers, benchmarks, coverage. 13. Tooling: the compiler flags, the full .clang-tidy profile with the reason for every disabled check, other checks, version pinning. The guide prescribes no test, fuzzing or benchmark framework and no error library; it prescribes how they are used. ADOPTING.md explains how a project points to the guide, copies the profiles and records its deviations. CONTRIBUTING.md explains how to propose a change. The text is licensed under CC BY 4.0. The configuration and code examples are dedicated to the public domain under CC0 1.0, so projects can copy them under their own license. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'Guide/TYPES-AND-CLASSES.md')
-rw-r--r--Guide/TYPES-AND-CLASSES.md137
1 files changed, 137 insertions, 0 deletions
diff --git a/Guide/TYPES-AND-CLASSES.md b/Guide/TYPES-AND-CLASSES.md
new file mode 100644
index 0000000..d1140a7
--- /dev/null
+++ b/Guide/TYPES-AND-CLASSES.md
@@ -0,0 +1,137 @@
+<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> -->
+<!-- SPDX-License-Identifier: CC-BY-4.0 -->
+
+# 6. Types and classes
+
+## 6.1 Struct or class
+
+A **struct** is a bundle of values with no invariant: any combination of field values is valid. Its data members are
+public, `camelCase`, and brace-initialized; it may have a defaulted comparison and small helpers that read its fields.
+
+```cpp
+/// One edge of a control-flow graph.
+struct Edge final
+{
+ /// Block the edge leaves.
+ BlockId from{};
+ /// Block the edge enters.
+ BlockId to{};
+
+ [[nodiscard]] auto
+ operator==(const Edge &) const -> bool = default;
+};
+```
+
+A **class** keeps an invariant: some combinations of its state are invalid, and only its member functions may change it.
+Its data members are `private` and end in `_`. A class has no `protected` data.
+
+If a struct gains an invariant, it becomes a class. Do not put an invariant into a struct with a comment asking callers
+to respect it.
+
+## 6.2 `final` by default
+
+Every class and struct is `final` unless it is designed as a base class. Designing for inheritance is a decision with
+consequences (a virtual destructor, a stable protected interface, documentation of what an override may do), and
+`final` makes the default decision visible.
+
+## 6.3 Construction
+
+- **A constructor establishes the invariant or does not exist.** When construction can fail, the constructor is private
+ and a static `Create` returns `Expected<Type>`:
+
+ ```cpp
+ [[nodiscard]] static auto
+ Create(std::size_t bitCount) -> llvm::Expected<BitSet>;
+ ```
+
+ A public constructor may still take values whose validity the caller guarantees by contract; it documents the
+ precondition and asserts it.
+
+- **`explicit`** on every single-argument constructor (see [Language](LANGUAGE.md#55-casts-and-conversions)).
+- **Initialize members in the declaration** where a default exists, and in the constructor's initializer list otherwise,
+ in declaration order. Do not assign in the constructor body what the initializer list can set.
+- **No work that can fail, and no virtual calls, in a constructor or a destructor.**
+- **No two-phase initialization** (`Init()` after construction): an object that exists is ready to use.
+
+## 6.4 Special members: the rule of zero
+
+Prefer members that manage themselves (values, `std::vector`, `std::unique_ptr`, an RAII handle), so that a class
+declares none of the copy and move operations or the destructor, and the defaults are right.
+
+When a class must manage a resource by hand, it declares all five: destructor, copy constructor, copy assignment, move
+constructor and move assignment, each either implemented, `= default` or `= delete`. Move operations are `noexcept`.
+
+```cpp
+class FileHandle final
+{
+public:
+ explicit FileHandle(int descriptor) noexcept;
+ ~FileHandle();
+
+ FileHandle(const FileHandle &) = delete;
+ auto
+ operator=(const FileHandle &) -> FileHandle & = delete;
+
+ FileHandle(FileHandle &&other) noexcept;
+ auto
+ operator=(FileHandle &&other) noexcept -> FileHandle &;
+
+private:
+ int descriptor_{-1};
+};
+```
+
+A type that must not be copied deletes the copy operations explicitly instead of inheriting from a "noncopyable" base.
+
+## 6.5 Inheritance and polymorphism
+
+- **Prefer composition, `std::variant` or a template over inheritance** for code reuse. Inherit to model "is a", for an
+ interface that several unrelated implementations provide.
+- **A polymorphic base** has a public virtual destructor, or a protected non-virtual one when objects are never deleted
+ through the base. It has no data members unless every derived class needs them.
+- **`override`** on every overriding function, and never `virtual` together with `override`. `final` on an override that
+ must not be overridden further.
+- **Public inheritance only.** Private inheritance is composition with a worse syntax.
+- **No multiple inheritance of implementation.** A class may implement several pure interfaces.
+- **Interfaces are small.** An interface with one implementation and no test double is not yet an interface.
+- **No RTTI.** Dispatch with virtual functions, `std::visit`, or a kind tag and LLVM-style `isa`/`cast`/`dyn_cast`.
+
+## 6.6 Operators
+
+- Overload an operator only when it means what it means for built-in types: `+` adds, `==` compares for equality, `[]`
+ indexes. A reader must never wonder what `a << b` does to `a`.
+- **Default comparisons**: `operator==` and, when there is an order, `operator<=>`, both `= default` where memberwise
+ comparison is right.
+- Binary operators that are symmetric are hidden friends or non-members, so both operands convert the same way.
+- No overloads of `&&`, `||`, `,` or unary `&`.
+
+## 6.7 Strong types
+
+Wrap a value whose meaning a bare integer or string would lose:
+
+```cpp
+/// Identity of one block in a function; distinct from every other id kind.
+struct BlockId final
+{
+ std::uint32_t value{};
+
+ [[nodiscard]] auto
+ operator<=>(const BlockId &) const = default;
+};
+```
+
+A function that takes a `BlockId` and a `SymbolId` cannot be called with the two swapped, and the compiler, not a review,
+catches it.
+
+## 6.8 `std::optional`, `std::variant` and sentinels
+
+- **`std::optional<T>`** for "a value or nothing", instead of a sentinel such as `-1`, `0` or an empty string.
+- **`std::variant`** for "one of several kinds", visited with `std::visit` and an overload set, so a new alternative
+ fails to compile where it is not handled.
+- **A pointer that may be null** is fine for an optional reference to an existing object: `const Symbol *FindSymbol(...)`
+ says "or nothing" just as well, without a copy.
+
+## 6.9 Enumerations as flags
+
+A set of flags is an `enum class` with the bit operators defined for it, or a `std::bitset` or a set type of the project.
+Do not combine plain integer constants with `|`.