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:
- cppreference.com - Attributes
- “C++17: The Complete Guide” by Nicolai Josuttis
- Compiler documentation (GCC, Clang, MSVC)
Related topics: nodiscard, deprecated, noreturn.
One-line summary: Attributes are C++11 standardized way to provide additional information to the compiler.