C++ Attributes | Complete 'Attributes' Guide

C++ Attributes | Complete 'Attributes' Guide

이 글의 핵심

How to use C++ attributes (nodiscard, deprecated, etc.) for compiler hints and warnings. Comprehensive guide to commonly used attributes in production with examples.

What are Attributes?

Attributes are a feature introduced in C++11, providing a standardized way to give additional information to the compiler. Used for code quality improvement, optimization hints, API documentation, etc.

Why needed?:

  • Code quality: Prevent bugs with compiler warnings
  • Optimization: Provide optimization hints to compiler
  • Documentation: Clearly express intent
  • Standardization: Use standard instead of compiler-specific extensions
// ❌ Compiler-specific extension: non-standard
#ifdef __GNUC__
    __attribute__((warn_unused_result))
#endif
int compute();

// ✅ Standard attribute: all compilers
[[nodiscard]] int compute();

[[nodiscard]]

Generates warning when return value is ignored. Used for functions returning error codes or important resources.

// Warning on ignored return value
[[nodiscard]] int compute() {
    return 42;
}

int main() {
    compute();  // Warning: return value ignored
    
    int result = compute();  // OK
}

Use scenarios:

  • Error codes: [[nodiscard]] ErrorCode save()
  • Resource handles: [[nodiscard]] FileHandle open()
  • Important calculations: [[nodiscard]] int calculate()
// Error code
[[nodiscard]] bool saveFile(const std::string& path) {
    // Save logic
    return true;
}

// ❌ Ignore error
saveFile("data.txt");  // Warning

// ✅ Check error
if (!saveFile("data.txt")) {
    std::cerr << "Save failed\n";
}

[[deprecated]]

Here is the oldFunc implementation:

// Deprecation warning
[[deprecated("use newFunc instead")]]
void oldFunc() {
    cout << "Old function" << endl;
}

void newFunc() {
    cout << "New function" << endl;
}

int main() {
    oldFunc();  // Warning: deprecated
    newFunc();  // OK
}

[[maybe_unused]]

Here is the func implementation:

void func([[maybe_unused]] int debug) {
    #ifdef DEBUG
        cout << "Debug: " << debug << endl;
    #endif
    // No warning when debug unused
}

int main() {
    [[maybe_unused]] int x = 10;
    // No warning when x unused
}

[[likely]] / [[unlikely]] (C++20)

Here is the process implementation:

int process(int x) {
    if (x > 0) [[likely]] {
        // Most common path
        return x * 2;
    } else [[unlikely]] {
        // Rare path
        return 0;
    }
}

[[fallthrough]]

Here is the process implementation:

void process(int x) {
    switch (x) {
        case 1:
            cout << "1" << endl;
            [[fallthrough]];  // Intentional fall-through
        case 2:
            cout << "1 or 2" << endl;
            break;
        case 3:
            cout << "3" << endl;
            // [[fallthrough]];  // No warning (last case)
    }
}

[[noreturn]]

Here is the main implementation:

[[noreturn]] void fatal(const string& msg) {
    cerr << "Fatal error: " << msg << endl;
    exit(1);
}

int main() {
    if (error) {
        fatal("Error occurred");
        // Never reaches here
    }
}

Practical Examples

Example 1: Error Handling

Here is the isOk implementation:

class [[nodiscard]] Result {
private:
    bool success;
    string message;
    
public:
    Result(bool s, string m) : success(s), message(m) {}
    
    bool isOk() const { return success; }
    string getMessage() const { return message; }
};

Result saveFile(const string& filename) {
    // File save logic
    return Result(true, "Save successful");
}

int main() {
    saveFile("test.txt");  // Warning: return value ignored
    
    auto result = saveFile("test.txt");  // OK
    if (!result.isOk()) {
        cout << result.getMessage() << endl;
    }
}

Example 2: API Version Management

Here is the processV1 implementation:

class API {
public:
    [[deprecated("use processV2 instead")]]
    void processV1(int data) {
        cout << "V1: " << data << endl;
    }
    
    void processV2(int data, bool flag = false) {
        cout << "V2: " << data << ", " << flag << endl;
    }
};

int main() {
    API api;
    
    api.processV1(10);  // Warning
    api.processV2(10);  // OK
}

Example 3: Optimization Hints

Here is the main implementation:

#include <random>

int main() {
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<> dis(1, 100);
    
    int x = dis(gen);
    
    if (x > 50) [[likely]] {
        // 50% probability (likely)
        cout << "Large number" << endl;
    } else [[unlikely]] {
        // 50% probability (unlikely inappropriate)
        cout << "Small number" << endl;
    }
    
    // Correct usage
    if (x > 95) [[unlikely]] {
        // 5% probability
        cout << "Very large number" << endl;
    }
}

Example 4: Switch Fall-through

Here is the execute implementation:

enum Command {
    CMD_INIT,
    CMD_START,
    CMD_STOP,
    CMD_CLEANUP
};

void execute(Command cmd) {
    switch (cmd) {
        case CMD_INIT:
            cout << "Initialize" << endl;
            [[fallthrough]];
        case CMD_START:
            cout << "Start" << endl;
            break;
        case CMD_STOP:
            cout << "Stop" << endl;
            [[fallthrough]];
        case CMD_CLEANUP:
            cout << "Cleanup" << endl;
            break;
    }
}

int main() {
    execute(CMD_INIT);
    // Initialize
    // Start
}

Compiler-Specific Support

Here is the fastFunc implementation:

// GCC/Clang
[[gnu::always_inline]]
inline void fastFunc() {}

// MSVC
[[msvc::forceinline]]
void fastFunc() {}

// Cross-platform
#ifdef __GNUC__
    [[gnu::always_inline]]
#elif _MSC_VER
    [[msvc::forceinline]]
#endif
inline void fastFunc() {}

Common Issues

Issue 1: Ignoring nodiscard

Here is the main implementation:

[[nodiscard]] int compute() {
    return 42;
}

int main() {
    (void)compute();  // Explicit ignore (no warning)
    
    compute();  // Warning
}

Issue 2: Misusing likely/unlikely

The following example demonstrates the concept in cpp:

// ❌ Wrong hint
if (x == 0) [[likely]] {  // Actually rare case
    // ...
}

// ✅ Correct hint
if (x != 0) [[likely]] {  // Actually common
    // ...
}

Issue 3: fallthrough Position

The following example demonstrates the concept in cpp:

switch (x) {
    case 1:
        cout << "1" << endl;
        // [[fallthrough]];  // Not here!
    [[fallthrough]];  // Here!
    case 2:
        cout << "2" << endl;
        break;
}

Attribute Combinations

Here is the oldFunc implementation:

[[nodiscard, deprecated("use newFunc")]]
int oldFunc() {
    return 42;
}

[[nodiscard]] [[deprecated]]
int oldFunc2() {
    return 42;
}

Production Patterns

Pattern 1: RAII Resources

The following example demonstrates the concept in cpp:

class [[nodiscard]] FileHandle {
    FILE* file_;
    
public:
    FileHandle(const char* path) : file_(fopen(path, "r")) {
        if (!file_) {
            throw std::runtime_error("Failed to open file");
        }
    }
    
    ~FileHandle() {
        if (file_) {
            fclose(file_);
        }
    }
    
    // Prevent copying
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
};

// Usage
// FileHandle("data.txt");  // Warning: immediately destroyed
auto file = FileHandle("data.txt");  // OK

Pattern 2: API Migration

Here is the query implementation:

class Database {
public:
    // Old version
    [[deprecated("use executeQuery instead")]]
    void query(const std::string& sql) {
        // Old implementation
    }
    
    // New version
    [[nodiscard]] Result executeQuery(const std::string& sql) {
        // New implementation
        return Result{};
    }
};

// Usage
Database db;
db.query("SELECT * FROM users");  // Warning: deprecated
auto result = db.executeQuery("SELECT * FROM users");  // OK

Pattern 3: Performance Optimization

Here is the processData implementation:

int processData(int* data, size_t size) {
    int sum = 0;
    
    for (size_t i = 0; i < size; ++i) {
        if (data[i] > 0) [[likely]] {
            // Mostly positive
            sum += data[i];
        } else [[unlikely]] {
            // Rarely negative
            sum -= data[i];
        }
    }
    
    return sum;
}

FAQ

Q1: When to use attributes?

A:

  • Code quality: Prevent bugs with compiler warnings
  • Optimization: Provide hints to compiler
  • Documentation: Clearly express intent
  • API management: deprecated, nodiscard
[[nodiscard]] bool save();  // Must check return value
[[deprecated]] void oldFunc();  // Deprecated

Q2: When to use nodiscard?

A:

  • Error codes: Must check return value
  • Resource handles: RAII objects
  • Important calculations: Result should not be ignored
[[nodiscard]] ErrorCode connect();
[[nodiscard]] FileHandle open();
[[nodiscard]] int calculate();

Q3: Effect of likely/unlikely?

A: Branch prediction optimization can provide slight performance improvement.

The following example demonstrates the concept in cpp:

// Benchmark example
// Without likely: 100ms
// With likely: 95ms (5% improvement)

if (x > 0) [[likely]] {
    // Frequently executed path
}

Q4: Do all compilers support them?

A: Standard attributes supported since C++11. Some added in C++17/20.

  • C++11: [[noreturn]], [[carries_dependency]]
  • C++14: [[deprecated]]
  • C++17: [[fallthrough]], [[nodiscard]], [[maybe_unused]]
  • C++20: [[likely]], [[unlikely]], [[no_unique_address]]

Q5: What happens if attributes are ignored?

A: Compiler ignores them. Not an error, may only generate warnings.

[[nodiscard]] int compute();

compute();  // Warning (ignorable)

Q6: Can multiple attributes be combined?

A: Yes.

Here is the oldFunc implementation:

[[nodiscard, deprecated("use newFunc")]]
int oldFunc() {
    return 42;
}

// Or separated
[[nodiscard]] [[deprecated]]
int oldFunc2() {
    return 42;
}

Q7: Custom attributes?

A: Possible with compiler-specific extensions, but not standard.

// GCC/Clang
[[gnu::always_inline]] void fastFunc();

// MSVC
[[msvc::forceinline]] void fastFunc();

Q8: Learning resources for attributes?

A:

Related topics: nodiscard, deprecated, noreturn.

One-line summary: Attributes are C++11 standardized way to provide additional information to the compiler.