C++ Attributes
/ 3 min read
Table of Contents
C++11 attributes provide a standardized way to give the compiler extra information about your code. They’re like compiler pragmas, but portable and standard.
What Attributes Actually Are
Attributes use the [[attribute]] syntax and provide hints to the compiler:
[[deprecated]]void old_function() { // Compiler will warn when this is used}
[[nodiscard]]int calculate() { return 42; // Compiler warns if return value is ignored}They don’t change behavior, just provide additional information.
Standard Attributes
C++11 started with a few, and each standard added more:
// C++11[[noreturn]]void terminate_program() { std::exit(1); // Never returns normally}
// C++14[[deprecated("Use new_function instead")]]void legacy_function();
// C++17[[nodiscard]]ErrorCode process_data();
[[fallthrough]] // In switch statementsswitch (value) { case 1: do_something(); [[fallthrough]]; case 2: do_something_else(); break;}
// C++20[[likely]]if (condition) { // Hint that this branch is more probable // Hot path}When I Actually Use Attributes
Most of the time, I use attributes for:
- Deprecating functions: Gradual migration from old APIs:
class DatabaseManager {public: [[deprecated("Use execute_query with timeout parameter")]] ResultSet execute_query(const std::string& sql) { return execute_query(sql, std::chrono::seconds(30)); }
ResultSet execute_query(const std::string& sql, std::chrono::seconds timeout) { // New implementation }};- Preventing ignored return values: For functions where the return value matters:
class FileManager {public: [[nodiscard]] bool create_directory(const std::string& path) { // Returns false on failure - caller should check! }
[[nodiscard]] std::optional<std::string> read_file(const std::string& filename) { // Returns empty optional on failure }};
// This will generate a warning:file_manager.create_directory("/tmp/mydir"); // Ignoring return value!
// Correct usage:if (!file_manager.create_directory("/tmp/mydir")) { // Handle error}- Switch statement fallthrough: Making intentional fallthrough clear:
enum class TokenType { Number, Operator, Identifier, Keyword };
void process_token(TokenType type) { switch (type) { case TokenType::Number: handle_number(); break;
case TokenType::Identifier: check_if_builtin(); [[fallthrough]]; // Intentional fallthrough to keyword handling
case TokenType::Keyword: handle_identifier_or_keyword(); break;
case TokenType::Operator: handle_operator(); break; }}- Performance hints: Helping the compiler optimize:
bool is_valid_user(const User& user) { [[likely]] if (user.is_active && user.has_valid_session()) { return true; // Most users are valid }
[[unlikely]] if (user.is_suspended()) { log_security_event("Suspended user attempted access"); return false; // Rare case }
return false;}Custom Attributes
Some compilers support additional attributes:
// GCC/Clang specific[[gnu::hot]] // Function is called frequently[[gnu::cold]] // Function is rarely called[[gnu::pure]] // Function has no side effects
// MSVC specific[[msvc::intrinsic]] // Compiler intrinsic functionThese aren’t portable, so I use them sparingly.
Real World Example
Here’s how I use attributes in a network protocol parser:
class ProtocolParser {public: [[nodiscard]] ParseResult parse_message(const std::byte* data, size_t length) { if (length < MIN_MESSAGE_SIZE) { [[unlikely]] return ParseResult::InvalidLength; }
// Parse message... return ParseResult::Success; }
[[deprecated("Use parse_message with explicit buffer management")]] ParseResult parse_message(const std::string& data) { return parse_message(reinterpret_cast<const std::byte*>(data.data()), data.size()); }
[[noreturn]] void handle_fatal_error(const std::string& message) { log_critical_error(message); std::terminate(); }};Attribute Syntax Rules
Attributes can go in different places:
// On declarations[[deprecated]] void func();
// On typesvoid process([[maybe_unused]] int param);
// On statements[[fallthrough]];
// Multiple attributes[[deprecated, nodiscard]]int calculate();The Pattern I Follow
I use attributes to make intent explicit:
// Mark functions that should not have ignored return values[[nodiscard]] bool initialize();[[nodiscard]] ErrorCode process();
// Mark deprecated APIs with migration hints[[deprecated("Use modern_api() instead")]] void legacy_api();
// Mark unlikely error pathsif (critical_resource == nullptr) { [[unlikely]] handle_out_of_memory();}
// Mark functions that never return[[noreturn]] void panic(const std::string& message);Attributes improve code quality by making assumptions and contracts explicit. They help the compiler optimize and help other developers understand the code.