C++ Attributes

An introduction to C++ attributes - small statements like [[nodiscard]] and [[deprecated]] that we can include in our code to give additional information to the compiler and other developers
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art showing a maid character
Ryan McCombe
Ryan McCombe
Posted

Attributes are small statements we can add to our code to give the compiler, or other developers, guidance. Attributes can be identified as they are wrapped in [[ and ]] - for example, [[nodiscard]]

Example use cases for attributes include stating which branch of an if statement is most likely to be taken at run time. This allows the compiler to optimize around that expectation.

Additionally, we can use attributes to generate compiler warnings if our code isn’t being used as expected. Let's see an example of that, starting with the [[nodiscard]] attribute.

C++ [[nodiscard]] Attribute

Let's consider the following example:

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

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

This is very 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 being called on line 6, that return value is being ignored. So, the call to Add is doing nothing except waste 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

Function Side Effects

Specifically, [[nodiscard]] is designed to prevent us from discarding the return value of a function that doesn’t have any side effects.

Side effects are things that the function does to modify our program, beyond just returning a value. Some examples of side effects include:

  • Logging into the console
  • Modifying variables outside the scope of the function - this can include modifying arguments that were passed by reference
  • Calling some other function that has side effects

If a function has side effects, it isn’t appropriate to mark it as [[nodiscard]], as the caller may be calling the function just for those side effects.

C++ [[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 inform 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 (Player->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 gives a performance benefit. Additionally, other developers reading our code can get a sense of the expected context.

For example, reading this example, we know that it is possible that our Resurrect function could be given a Target that is already alive, but it’s not likely.

Deprecating C++ Functions with the [[deprecated]] Attribute

The final attribute we want to cover is [[deprecated]]. It is common for us to want to change or remove a function.

But, that function may be used hundreds of even thousands of places. This is particularly the case when we’re working on a large project, or working on a library that is being used in other projects.

For those scenarios, we can mark our function as deprecated:

[[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 be done instead? We can update our [[deprecated]] attribute to include a message:

[[deprecated(
  "StartQuest will be removed in the next version. "
  "See https://www.example-site.com/123 for alternatives."
)]]
void StartQuest(Character* Player) {
  // ...
}
'StartQuest' is deprecated:
StartQuest will be removed in the next version.
See https://www.example-site.com/123 for alternatives

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. We'll see them quite a lot when working in larger code bases. Additionally, we'll start scattering them into our code examples from this point forward, so we build some familiarity on when we can use them.

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Clean Code
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

C++ Static Analysis Tools

Learn how static analysis tools can suggest improvements to our code, helping us learn faster
3D art showing a maid cleaning
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved