[[nodiscard]]
, [[likely]]
, and [[deprecated]]
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.[[nodiscard]]
AttributeLet'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
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 the state of our program, beyond just returning a value. Some examples of side effects include:
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.
[[likely]]
and [[unlikely]]
AttributesWhen 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 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.
A more advanced form of this technique commonly used on larger projects is profile-guided optimization or PGO.
Our program might have a function that is called hundreds or thousands of times per second, whilst another is rarely ever called. The compiler should arrange our code to optimize the performance of the function that is called thousands of times per second.
But the compiler doesn’t inherently know how often our functions will be called at runtime.
With PGO, we first compile our project with additional data collection enabled. We then run our program and use it as normal, whilst the data collection generates a detailed profile of what code paths are being executed.
This profile records things like how often each function is called, which branches are most commonly taken, and how often loops iterate.
Once we have enough data in our profile, we then stop running our program, and compile it again. This second compilation uses the profile we generated from running the program.
The compiler can then optimize around how our program is used, guided by the insights from this profile
[[deprecated]]
AttributeThe 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 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 it, it’s common to instead mark it as deprecated. This allows our function to still be used, but anyone using it will get a warning asking them to update their code.
Then, after some time, we can remove the function entirely, with less disruption. We can do this 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) {
// ...
}
void StartQuest(QuestJournal* Journal) {
// ...
}
warning C4996: 'StartQuest': StartQuest(Character*) will be removed soon - switch to StartQuest(QuestJournal*)
By default, Visual Studio’s compiler treats invocations of a [[deprecated]]
function as an error, rather than a warning. This prevents us from building our project.
We can change this by opening the Project menu from the top bar, and then visiting Properties > C/C++ > Command Line. On this panel, under Additional Options, we can add: /sdl /w34996
to have the compiler treat this error as a warning instead.
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.
In this lesson, we've explored the concept of attributes. The key things we learned included:
[[nodiscard]]
to prevent discarding important return values and to catch potential errors.[[likely]]
and [[unlikely]]
attributes guide the compiler for optimization by indicating the expected path in conditional logic.[[deprecated]]
attribute to manage the evolution of code by marking functions or entities for future removal.In the upcoming lesson, we delve into the world of randomness in C++. This allows us to introduce non-deterministic results in our program, which we demonstrate in the context of a combat system for a role-playing game. The key topics we’ll cover include**:**
std::mt19937
.Explore the fundamentals of attributes, including [[nodiscard]]
, [[likely]]
, and [[deprecated]]
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way