gemini-2.5-flash-preview-04-17| input-price: 0.15 output-price: 3.5 max-context-length: 128_000 (cost: $0.01) Β· Anfrage: 2025-05-06T19:59:01.828966
Generated by rs-summarizer Version unbekannt
Abstract:
This presentation by Matt Godbolt at C++ on Sea explores techniques for designing C++ APIs using a "correct by construction" approach, aiming to prevent misuse and catch errors at compile time. Drawing on experience in demanding industries, Godbolt demonstrates how to leverage C++ features such as strong value types, enum class, static_assert, RAII (Resource Acquisition Is Initialization), [[nodiscard]] attributes, and r-value reference qualified methods (&&) to create APIs that guide the user towards correct usage and make errors difficult. The talk also highlights the importance of enabling compiler warnings and using static analysis tools. Examples are used from finance, GUI development, and shader compilation.
Designing C++ APIs for Correctness: Leveraging the Compiler
int or double for distinct concepts (like quantity and price) can lead to silent, expensive errors if arguments are accidentally swapped, which default compiler warnings often miss.using or typedef are just aliases and don't create new types or prevent swaps (0:06:05).Price and Quantity (0:06:32). This forces the compiler to see them as incompatible types.explicit constructors for single-argument constructors (e.g., explicit Quantity(int val);) to prevent unintended implicit conversions from primitive types (0:07:50).Quantity which shouldn't be negative:
unsigned int internally is insufficient; it requires enabling warnings like -Wsign-conversion.static_assert with type traits (like std::is_unsigned_v<T>) in templated constructors or C++20 concepts (std::unsigned_integral) to prevent construction from signed/negative types at compile time (0:11:40). This might necessitate using 100u literals.static Quantity Quantity::from(int val)) with runtime checks (e.g., val >= 0) or gsl::narrow for safe conversions (0:14:00).100_qty, 1000.0_dollars).consteval for UDLs (C++20): Marking a UDL implementation consteval forces its evaluation at compile time, turning errors like out-of-range values into compilation failures.double; use integer types representing the smallest unit (e.g., pennies).enum class: Replace boolean flags with enum class (e.g., enum class OrderSide { Buy, Sell };) for clarity, type safety, and self-documentation.static_assert): Replace comments stating invariants (e.g., "// Must be 13 bytes long") with static_assert(sizeof(Header) == 13, "..."); to enforce them at compile time.enum class for Protocol/State Types: Use enum class MessageType : char { Add = 'A', ... }; for message types in binary protocols or state machine states, providing type safety and improving readability over raw characters or integers.switch Statements without default: When switching on an enum class, omitting the default case (with appropriate warnings enabled, e.g., -Wswitch-enum) allows the compiler to issue an error if a new enum value is added and not handled in the switch, helping locate all necessary code updates.lock() method should return an RAII object (e.g., ScopedLock) that acquires the lock in its constructor and releases it in its destructor, automating the unlock process and handling exceptions.[[nodiscard]] Attribute: Apply this attribute to the RAII object or the method returning it to prevent the caller from accidentally ignoring the returned object, which would cause the resource to be released prematurely.Tinkerable or LockedWidget) from the state-acquiring method (lock()). This wrapper holds the state (the lock) and is the only object through which dependent operations (tinker_with()) can be called.widget.tinker([](WidgetState& state){ ... });) that acquires the necessary state internally (the lock), then executes a provided lambda, passing access to the internal state, ensuring the state is held only for the duration of the lambda.ShaderCompiler vs. CompiledShaders).&&) on Methods: Applying && to a method (e.g., CompiledShaders compile() &&;) restricts it to being called only on an r-value (a temporary object or one explicitly std::move'd). This signals that the object is consumed by the operation.
compile() on a local variable explicitly requires std::move(compiler).compile();. Returning the compiler object itself (return compiler.compile();) might allow implicit move depending on context.subscribe() method can return an RAII object (Subscription) that registers the listener in its constructor and unregisters in its destructor, automating the unsubscribe process.