Attributes

Explore the fundamentals of attributes, including [[nodiscard]], [[likely]], and [[deprecated]]

Ryan McCombe
Updated

Attributes are small statements we can add to our code to give the compiler, or other developers, guidance. Attributes can be identified by the [[ and ]] that surround them.

There are many attributes we can use, and more are being added regularly. We cover some of the most useful ones in this lesson:

  • [[nodiscard]] indicates that we expect a function's return value to be used. If it is discarded, the compiler will generate a warning
  • [[likely]] and [[unlikely]] within conditional logic give the compiler guidance over which code path we expect to be taken, allowing it to optimize around the likely path
  • [[deprecated]] gives other developers guidance that we'd prefer they not use a specific function or class, as we intend to remove it soon.

The [[nodiscard]] Attribute

Let's consider the following code:

int Add(int x, int y) {
  return x+y;
}

int main() {
  Add(1, 2);
}

This is simple and compiles as expected. However, if someone wrote code like this, they certainly made an error.

This is because the only thing our Add function does is return a value. Yet, when Add is called, that return value is being ignored.

So, the call to Add isn't doing anything. The developer probably misunderstood how our function works and is about to introduce a bug, or may just be wasting resources.

If we annotate the Add function with a [[nodiscard]] attribute, our program still works as expected, but the compiler now helpfully warns us of the issue:

[[nodiscard]]
int Add(int x, int y) {
  return x+y;
}

int main() {
  Add(1, 2);
}
warning: Ignoring return value of function declared with 'nodiscard' attribute

The [[likely]] and [[unlikely]] Attributes

When we're creating conditional logic, sometimes one of our branches is much more likely to be followed. When we know that, we can provide that insight to the compiler, using [[likely]]:

void StartQuest(Character* Player) {
  if (Player->IsAlive()) [[likely]] {
    // ...
  }
  // ...
}

Similarly, we can inform the compiler that a condition is unlikely to be true using the [[unlikely]] attribute:

void Resurrect(Character* Target) {
  if (Target->IsAlive()) [[unlikely]] {
    // ...
  }
  // ...
}

When the compiler knows a path is going to be very commonly used within our function, it can optimize the compilation of that function accordingly. This results in a performance improvement.

Additionally, other developers reading our code can get a sense of the expected context. In the previous example, our code suggests that it is possible that our Resurrect function could be given a Target that is already alive, but it's not likely.

The [[deprecated]] Attribute

The final attribute we want to cover is [[deprecated]]. When something is "deprecated", it still works, but we'd rather people not use it.

For example, we might want to remove a function that has a subtle bug, performance issue, or is just difficult for us to maintain. However, that function may be used in hundreds or thousands of other places. This is particularly common when we're working on a large project with colleagues, or working on a library that is being used in other projects.

Instead of attempting to remove or change a function that is used in many parts of the code, including parts of the code that are "owned" by other developers or other teams, it's instead common to mark the function as deprecated.

A deprecated function can still be used and it works as it always has but, when it is used, a warning will be emitted asking the developer to update their code to stop using it and implement their logic in some other way. Then, after some time, and hopefully after everyone has heeded the warning and stopped using the function, we can remove it entirely.

We can mark a function as deprecated using the [[deprecated]] attribute:

[[deprecated]]
void StartQuest(Character* Player) {
  // ...
}

Now, code that calls this function will still work, but they will get a compiler warning:

'StartQuest' is deprecated

Generally, we will want to give more information - for example, what should the consumer use instead? We can update our [[deprecated]] attribute to include a message:

[[deprecated(
  "StartQuest(Character*) will be removed "
  "soon - switch to StartQuest(QuestJournal*)"
)]]
void StartQuest(Character* Player) {
  // The function we want to remove
  // ...
}

void StartQuest(QuestJournal* Journal) {
  // The function we want to replace it with
  // ...
}
warning C4996: 'StartQuest': StartQuest(Character*) will be removed soon - switch to StartQuest(QuestJournal*)

There are many more attributes we can use. Additionally, compilers often introduce their own, non-standard attributes, which we can include in our code if we're using that compiler.

However, this summary introduced the most common and useful attributes.

Summary

In this lesson, we've explored the concept of attributes. The key things we learned included:

  • Learned the use of [[nodiscard]] to prevent discarding important return values and to catch potential errors.
  • Understood how [[likely]] and [[unlikely]] attributes guide the compiler for optimization by indicating the expected path in conditional logic.
  • Explored the [[deprecated]] attribute to manage the evolution of code by marking functions or entities for future removal.
  • Gained insights into the importance of compiler-guided optimization and how attributes contribute to efficient code execution.
  • Discussed the concept of function side effects and when it's appropriate to use certain attributes.
Next Lesson
Lesson 54 of 60

Random Number Generation

This lesson covers the basics of using randomness, with practical applications

Have a question about this lesson?
Answers are generated by AI models and may not have been reviewed for accuracy