Tag: c++

Entries for tag "c++", ordered from most recent. Entry count: 150.

Pages: 1 2 3 ... 19 >

# Debugging third-party apps in Visual Studio

Mon
29
Apr 2024

If you are a programmer coding mostly in C++ for Windows, as I do, you likely use Microsoft Visual Studio as the IDE, including its code editor and debugger. When using Visual Studio for development, we typically compile, launch, and debug the code we developed. However, Visual Studio can also be used for debugging third-party executables. Having neither debug symbols (.pdb files) nor the source code of the application will result in a limited debugging experience, but it can still be useful in some cases. In this article, I will share a few tips and tricks for using the Visual Studio debugger to debug external .exe files.

I think this article is suitable for programmers with all levels of experience, also for beginners. We won't be looking at the x86 assembly code, I promise! 😀 All the screenshots are made with Visual Studio 2022 version 17.9.6 on Windows 10.

 

 

 

Comments | #c++ #visual studio Share

# 3 Ways to Iterate Over std::vector

Sat
30
Sep 2023

This will be a short article about basics of C++. std::vector is a container that dynamically allocates a continuous array of elements. There are multiple ways to write a for loop to iterate over its elements. In 2018 I've written an article "Efficient way of using std::vector" where I compared their performance. I concluded that using iterators can be orders of magnitude slower than using a raw pointer to its data in Debug configuration. This time, I would like to focus on how using "modern" C++ also limits our freedom.

Language purists would probably say that the recommended way to traverse a vector is now a range-based for loop, available since C++11. This is indeed the shortest and the most convenient form, but inside the loop it gives access only to the current element, not its index and not any other elements.

struct Item
{
  int number;
int otherData[10];
};

std::vector<Item> items = ...

int numberSum = 0;
for(const Item& item : items)
numberSum += item.number;

Imagine that while traversing the vector, for some elements that are not the first and that meet certain criteria, we want to compare them with their previous element. This is not possible in a range-based for loop above, unless we memorize the previous element in a separate variable and update it on every iteration. Using iterators gives us the possibility to move forward or backward and thus to access the previous element when needed.

for(std::vector<Item>::const_iterator currIt = items.begin(); currIt != items.end(); ++currIt)
{
if(currIt != items.begin() && // Not the first
MeetsCriteria(*currIt))
{
    std::vector<Item>::const_iterator prevIt = currIt;
--prevIt; // Step back to the previous element
CompareWithPrevious(*prevIt, *currIt);
}
}

This is more flexible, but what if we want to insert some elements to the vector while traversing it? There is a trap awaiting here because insert method may invalidate all iterators when underlying array gets reallocated. This is why only iterating using an index is safe here:

for(size_t index = 0; index < items.size(); ++index)
{
Item newItem;
if(NeedInsertItemBefore(items[index], &newItem))
{
items.insert(items.begin() + index, newItem);
++index;
}
}

Note that pretty much any modern programming language allows to insert and remove elements from a dynamic array using an index, e.g.:

Only C++ requires clumsy syntax with iterators like items.begin() + index.

I know that the code fragments shown above can be written in many other ways, e.g. using auto keyword. If you have an idea for writing any of these loops better way, please leave a comment below and let's discuss.

Comments | #c++ Share

# Launching process programmatically: system vs CreateProcess vs ShellExecute

Sat
15
Apr 2023

Today I went on a quest to investigate various ways in which we can launch a process (an EXE file) programmatically, while programming in C++ using Windows. I tested 3 different functions: system, CreateProcess, ShellExecute. I focused on ways to specify a path to the executable file – not on passing parameters and not on capturing standard input/output of the subprocess (which I plan to investigate next and I did). All examples below launch a subprocess and wait until it completes before continuing. They all make the subprocess inheriting the console, so if both main process and the subprocess are console programs, their output will go to the single console of the main process.

But first things first: To understand this article, please recall that in operating systems we commonly use, no matter if Windows or Linux, every executable file launched as a process has several parameters:

Paths in the file system can be absolute (in case of Windows it usually means they start with drive letter, like “C:\Dir1\Text.exe”) or relative.

Startup directory is often the same as the directory where the executable file is located, but it doesn’t need to be. Many methods of process launching offer an explicit parameter for it. We won’t use it in the code samples below, but you can also achieve this manually from system console. For example, following console command uses a relative path to launch an executable located in “C:\Dir2\Test.exe”, while current directory of the process will be the same as current directory of the console: “C:\Dir1”:

C:\Dir1>..\Dir2\Test.exe

Method 1: Function system from standard C library (required header: <stdlib.h> or <cstdlib> in C++) is the simplest, most primitive one. It just takes a single string as parameter. An advantage of it is that you can launch any console command with it, also built-in commands (like “echo”), not only EXE files. It is also portable between different operating systems.

#include <cstdlib>

int main()
{
    char path[MAX_PATH];
    strcpy_s(path, "Test.exe");

    system(path);
}

Waiting for the command to finish is the default behavior of this function and so is inheriting the console, so that messages printed to the standard output by “Test.exe” will go to the same console as our host application.

path can always be absolute or relative. For each of the 4 methods described in this article, I found answers to following questions:

  1. Does it work with file name alone without an extension, like "Test"? In case of this function: Yes, it does.
  2. When only file name (relative path) is specified, like "Test.exe", where is the function able to locate the executable file?
    1. In the same directory where host EXE is located? No.
    2. In the current directory? Yes.
    3. In one of the directories passed though PATH environmental variable? Yes.
  3. When the path contains spaces, like "C:\My Program\Test.exe", how to escape it properly?
    1. Does it work as-is without any escaping, like strcpy_s(path, "C:\\My Program\\Test.exe");? No. (Note the double backslash \\ is for escaping in C++, so that string will actually contain single backslashes. You can also use forward slashes / in Windows – they work with all methods described in this article and they don’t need to be escaped in C++ code.)
    2. Does it work when entire path is enclosed with quotes, like strcpy_s(path, "\"C:\\My Program\\Test.exe\"");? Yes.
    3. Does it work when spaces are escaped with character ^, like strcpy_s(path, "C:\\My^ Program\\Test.exe");? Yes! (However strange it looks, this is the character used as an escape sequence in Windows shell!)

Method 2: Function CreateProcess from WinAPI (required header: <Windows.h>) is likely the most native and most feature-rich option. Numerous parameters passed to the function and accompanying structures allow to control the new subprocess in various ways, including getting and using its process handle or capturing its standard input/output. Here, for simplicity, I replicate the behavior of system function from method 1 – I make it inherit the console by passing parameter bInheritHandles = TRUE and wait until it completes by calling WaitForSingleObject on the process handle. Process handle and main thread handle also need to closed to avoid resource leak.

STARTUPINFO startupInfo = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION processInfo = {};
BOOL success = CreateProcess(
  path, // lpApplicationName
  NULL, // lpCommandLine
  NULL, // lpProcessAttributes
  NULL, // lpThreadAttributes
  TRUE, // bInheritHandles
  0, // dwCreationFlags
  NULL, // lpEnvironment
  NULL, // lpCurrentDirectory
  &startupInfo,
  &processInfo);
assert(success);
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);

There are actually 2 ways to pass executable file path to CreateProcess. Code above shows the first way – using lpApplicationName parameter, which is intended for just application name, while command line parameters are passed via next argument. Note this is different from system function, which accepts one string with everything. Using the method shown above:

  1. Does it work with file name alone without an extension, like "Test"? No.
  2. When only file name is specified, like "Test.exe", where is the function able to locate the executable file?
    1. In the same directory where host EXE is located? No.
    2. In the current directory? Yes.
    3. In one of the directories passed though PATH environmental variable? No!
  3. When the path contains spaces, how to escape it properly?
    1. Does it work as-is without any escaping, like "C:\\My Program\\Test.exe"? Yes – likely because this parameter is intended exclusively for executable file path.
    2. Does it work when entire path is enclosed with quotes, like "\"C:\\My Program\\Test.exe\""? No.
    3. Does it work when spaces are escaped with character ^, like "C:\\My^ Program\\Test.exe"? No.

Method 3: Function CreateProcess, but this time passing executable file path as lpCommandLine parameter, while leaving lpApplicationName set to NULL. This is also a valid use case and it behaves differently – more like launching a console command than starting a specific EXE file.

STARTUPINFO startupInfo = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION processInfo = {};
BOOL success = CreateProcess(
  NULL, // lpApplicationName <- !!!
  path, // lpCommandLine <- !!!
  NULL, // lpProcessAttributes
  NULL, // lpThreadAttributes
  TRUE, // bInheritHandles
  0, // dwCreationFlags
  NULL, // lpEnvironment
  NULL, // lpCurrentDirectory
  &startupInfo,
  &processInfo);
assert(success);
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
  1. Does it work with file name alone without an extension, like "Test"? Yes!
  2. When only file name is specified, like "Test.exe", where is the function able to locate the executable file?
    1. In the same directory where host EXE is located? Yes!
    2. In the current directory? Yes.
    3. In one of the directories passed though PATH environmental variable? Yes!
  3. When the path contains spaces, how to escape it properly?
    1. Does it work as-is without any escaping, like "C:\\My Program\\Test.exe"? No!
    2. Does it work when entire path is enclosed with quotes, like "\"C:\\My Program\\Test.exe\""? Yes.
    3. Does it work when spaces are escaped with character ^, like "C:\\My^ Program\\Test.exe"? No!

Method 4: Function ShellExecuteEx (or legacy ShellExecute) which is also part of WinAPI, but coming from header <shellapi.h>. It requires COM to be initialized with CoInitializeEx. It can be used not only to start processes from EXE files, but also to open any types of files (TXT or DOCX documents, JPEG images etc.) with their associated programs, as if the user double-clicked on such file or right-clicked and selected one of the available “verbs”, like “Edit” or “Print”. But for this article, let’s focus on launching executable files. To replicate the same behavior as in previous methods, I pass SEE_MASK_NO_CONSOLE to inherit console and SEE_MASK_NOCLOSEPROCESS to retrieve process handle to be able to wait for it.

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
SHELLEXECUTEINFO shellExecuteInfo = {
  .cbSize = sizeof(SHELLEXECUTEINFO),
  .fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
  .lpFile = path,
  .nShow = SW_SHOWNORMAL
};
BOOL success = ShellExecuteEx(&shellExecuteInfo);
assert(success);
WaitForSingleObject(shellExecuteInfo.hProcess, INFINITE);
CloseHandle(shellExecuteInfo.hProcess);

This method behaves in the following way:

  1. Does it work with file name alone without an extension, like "Test"? Yes.
  2. When only file name is specified, like "Test.exe", where is the function able to locate the executable file?
    1. In the same directory where host EXE is located? No!
    2. In the current directory? Yes.
    3. In one of the directories passed though PATH environmental variable? Yes.
  3. When the path contains spaces, how to escape it properly?
    1. Does it work as-is without any escaping, like "C:\\My Program\\Test.exe"? Yes.
    2. Does it work when entire path is enclosed with quotes, like "\"C:\\My Program\\Test.exe\""? Yes.
    3. Does it work when spaces are escaped with character ^, like "C:\\My^ Program\\Test.exe"? No.

To summarize, let’s see all the results in a table:

  system() CreateProcess()
lpApplicationName
CreateProcess()
lpCommandLine
ShellExecuteEx()
Works without extension? "Test" YesNoYesYes
Searching dir of the host app? NoNoYesNo
Searching current dir? YesYesYesYes
Searching PATH env var? YesNoYesYes
Path with spaces unescaped: My Program\Test.exe NoYesNoYes
Path with spaces enclosed with quotes: "My Program\Test.exe" YesNoYesYes
Spaces escaped with ^: My^ Program\Test.exe YesNoNoNo

I did my tests using Windows 10, Version 22H2 (OS Build 19045.2846) and Visual Studio 2022 17.5.3. Although unlikely, it is not impossible that these results may change on another version of the operating system or C++ compiler and standard library implementation.

Comments | #windows #c++ #winapi Share

# Book review: C++ Initialization Story

Mon
27
Mar 2023

Courtesy its author BartÅ‚omiej Filipek (author of cppstories.com website), I was given an opportunity to read a book “C++ Initialization Story". Below you will find my review.

How many ways are there to initialize a variable in C++? I can think of at least the following:

int i1;
int i2; i2 = 123;
int i3 = 123;
int i4(); // function declaration not a variable
int i5(123);
int i6 = int(123);
int i7{};
int i8 = {};
int i9 = int{};
int iA{123};
int iB = {123};
int iC = int{123};

Do you know the difference between them? Which variable stays uinitialized, which is initialized with a value 0 or 123? What if I used a custom type instead of the basic int? How many copies of the object would be created? What if that type was a class having some custom constructors? Which constructor would get called? What if it was a std::vector or some other container?

Question like this is the foundation of this book, but topics covered by it are much wider. This book is a relatively big one. On 279 pages, the author treats the topic of "initialization" as an opportunity to describe various concepts of C++ language. Modern versions of the language standard are covered, up to C++23, but features that require new versions are explicitly marked as such. The book is not about some exotic quirks and tricks that can be done by stretching the language to its limits, but it is about concepts that are fundamental in any C++ program.

Initialization of local variables, as shown in the code above, is just the subject of the first chapter. Then initialization of "non-static data members" is described, which basically means variables inside structures and classes. Constructors obviously play the major role here, so their syntax and behavior is also described in details here. When talking about constructors, description of assignment operators and destructors follows naturally. Of course, these language constructs are described also in light of move semantics introduced by C++11. For example, did you know that std::vector<T> on resize will be able to use move constructor of your type T instead of performing a copy only when the move constructor is marked as noexcept?

Another topic related to initialization is an automatic deduction of types: auto keyword and template arguments. Special kinds of variables - static and thread_local are also described. The book also teaches new language constructs added for convenient variable initialization, like structured binding, designated initializers, or static inline. If you only used the old version if C++ so far, do you know that following syntax is now possible? Do you know what it means?

auto[iter, inserted] = mySet.insert(10);

Point p {
    .x = 10.0,
    .y = 20.0
};

class C {
    static inline int classCounter = 0;

When it comes to the difficulty level of the book, I would call it intermediate. Only some knowledge of C++ is required. Author explains every topic covered from very basics and shows simple code samples. The book additionally features a quiz in the middle and at the end, as well as a chapter with "techniques and use cases". For example, did you know that the most robust and efficient way to initialize a class with a string is to pass it by... value?

struct StringWrapper {
    std::string str_;
    StringWrapper(std::string str) : str_{std::move(str)} { }

For a long time I've been skeptical about new language standards like C++11, C++14, C++17, C++20. C++ is a tough language already, so every fresh addition only adds more complexity to it. It used to remind me of some elaborated, tricky Boost-style templates. But now, the more I use new features of the language (at least in my personal code), the more I like it. I always liked RAII and unique_ptr, but now with move semantics, return value optimization, std::optional, std::variant, and many other additions to the language small and big, it all starts to fit together. Code is clean, concise, readable, safe (no explicit new or delete!), and efficient at the same time. I now think that it is not an inherent feature of C++ to be verbose (with tons of boilerplate code required) and unsafe (with memory access violation errors easy to make), it is the old-fashioned approach of treating is as "C with classes". I hope that over time more and more developers, especially those who make key decisions in software projects, will also notice that and will allow using modern C++.

The book can be bought as ebook on leanpub.com, as well as in printed version on Amazon. I can strongly recommend it - it is really good! See also my reviews of previous book by this author: "C++17 in Detail" and "C++ Lambda Story".

Comments | #books #c++ Share

# Why I Catch Exceptions in Main Function in C++

Sun
22
Jan 2023

Exception handling in C++ is a controversial topic. On one hand, it can be a good means of reporting and handling errors if done correctly. For it to be free from memory leaks, all memory allocations should be wrapped in smart pointers and other acquired resources (opened files and other handles) wrapped in similar RAII objects. On the other hand, it has been proven many times that the exception mechanism in C++ works very slow. Disabling exception handling in C++ compiler options can speed up the program significantly. No wonder that game developers dislike and disable them completely.

Let’s talk about a command-line C++ program that doesn’t need to disable exception handling in compiler options. Even if it doesn’t use exceptions explicitly, some exceptions may occur, thrown by C++ standard library or some third-party libraries. When a C++ exception is thrown and uncaught, program terminates and process exit code is some large negative number. On my system it is -1073740791 = 0xC0000409.

It would be good if the program printed some error message in such case and returned some custom, clearly defined exit code. Therefore, when developing a command-line C++ program, I like to catch and handle exceptions in the main function, like this:

#include <exception>
#include <cstdio>

enum PROGRAM_EXIT {
    PROGRAM_EXIT_SUCCESS   =  0,
    PROGRAM_EXIT_FAILED    = -1,
    PROGRAM_EXIT_EXCEPTION = -2
};

int ActualProgram(int argc, char** argv) {
    ...
}

int main(int argc, char** argv) {
    try {
        return ActualProgram(argc, argv);
    }
    catch(const std::exception& ex) {
        fprintf(stderr, "ERROR: %s\n", ex.what());
        return PROGRAM_EXIT_EXCEPTION;
    }
    catch(...) {
        fprintf(stderr, "UNKNOWN ERROR.\n");
        return PROGRAM_EXIT_EXCEPTION;
    }
}

Besides that, if you develop for Windows using Visual Studio, there is another, parallel system of throwing and catching exceptions, called Structured Exception Handling (SEH). It allows you to handle “system” errors that are not C++ exceptions and would otherwise terminate your program, even when using code shown above. This kind of error can be memory access violation (using null or incorrect pointer) or integer division by zero, among others. To catch them, you can use the following code:

#include <Windows.h>

int main(int argc, char** argv) {
    __try {
        return main2(argc, argv);
    }
    __except(EXCEPTION_EXECUTE_HANDLER) {
        fprintf(stderr, "STRUCTURED EXCEPTION: 0x%08X.\n",
            GetExceptionCode());
        return PROGRAM_EXIT_EXCEPTION;
    }
}

Few additional notes are needed here. First, SEH __try-__except section cannot exist in one function with C++ try-catch. It is fine, though, to call a function doing one way of error handling from a function doing the other one. Their order is important – C++ exceptions are also caught by SEH __except, but SEH exceptions are not caught by C++ catch. So, to do it properly, you need to make your main function doing SEH __try-__except, which calls some main2 function doing C++ try-catch, which calls ActualProgram – not the other way around.

If you wonder what are the process exit codes returned by default when exceptions are not caught, the answer can be found in the documentation of GetExceptionCode macro and Windows header files. When memory access violation occurs, this function (or the entire process, if SEH exceptions are not handled) returns -1073741819 = 0xC0000005, which matches EXCEPTION_ACCESS_VIOLATION. When a C++ exception is thrown, the code is -1073740791 = 0xC0000409, which is not one of EXCEPTION_ symbols, but I found it defined as STATUS_STACK_BUFFER_OVERRUN (strange…). Maybe it would be a good idea to extend the __except section shown above to decode known exception codes and print their string description.

Finally, you need to know that integer division by zero throws a SEH exception, but floating-point division by zero does not – at least not by default. There is EXCEPTION_FLT_DIVIDE_BY_ZERO and EXCEPTION_INT_DIVIDE_BY_ZERO error code defined, but the default behavior of incorrect floating-point calculations (e.g. division by zero, logarithm of a negative value) is to return special values like Not a Number (NaN) or infinity and proceed with further calculations. This behavior can be changed, as described in “Floating-Point Exceptions”.

Comments | #windows #visual studio #c++ Share

# DivideRoundingUp Function and the Value of Abstraction

Sun
25
Sep 2022

It will be a brief article. Imagine we implement a postprocessing effect that needs to recalculate all the pixels on the screen, reading one input texture as SRV 0 and writing one output texture as UAV 0. We use a compute shader that has numthreads(8, 8, 1) declared, so each thread group processes 8 x 8 pixels. When looking at various codebases in gamedev, I've seen many times a code similar to this one:

renderer->SetConstantBuffer(0, &computeConstants);
renderer->BindTexture(0, &inputTexture);
renderer->BindUAV(0, &outputTexture);
constexpr uint32_t groupSize = 8;
renderer->Dispatch(
    (screenWidth  + groupSize - 1) / groupSize,
    (screenHeight + groupSize - 1) / groupSize,
    1);

It should work fine. We definitely need to align up the number of groups to dispatch, so we don't skip pixels on the right and the bottom edge in case our screen resolution is not a multiply of 8. The reason I don't like this code is that it uses a "trick" that in my opinion should be encapsulated like this:

uint32_t DivideRoundingUp(uint32_t a, uint32_t b)
{
    return (a + b - 1) / b;
}
...
renderer->Dispatch(
    DivideRoundingUp(screenWidth, groupSize),
    DivideRoundingUp(screenHeight, groupSize),
    1);

Abstraction is the fundamental concept of software engineering. It is hard to work with complex systems without hiding details behind some higher-level abstraction. This idea applies to entire software modules, but also to small, 1-line pieces of code like this one above. For some programmers it might be obvious when seeing (a + b - 1) / b that we do a division with rounding up, but a junior programmer who just joined the team may not know this trick. By moving it out of view and giving it a descriptive name, we make the code cleaner and easier to understand for everyone. Therefore I think that all small arithmetic or bit tricks like this should be enclosed in a library of functions rather than used inline. Same with the popular formula for checking if a number is a power of two:

bool IsPow2(uint32_t x)
{
    return (x & (x - 1)) == 0;
}

Comments | #software engineering #algorithms #c++ Share

# Intrusive Linked List in C++

Tue
25
May 2021

A doubly linked list is one of the most fundamental data structures. Each element contains, besides the value we want to store in this container, also a pointer to the previous and next element. This may not be the best choice for indexing i-th element or even traversing all elements quickly (as they are scattered in memory, performance may suffer because of poor cache utilization), but inserting an removing an element from any place in the list is quick.


Source: Doubly linked list at Wikipedia.

Inserting and removing elements is quick, but not necessarily very simple in terms of code complexity. We have to change pointers in the current element, previous and next one, as well as handle special cases when the current element is the first one (head/front) or the last one (tail/back) – a lot of special cases, which may be error-prone.

Therefore it is worth to encapsulate the logic inside some generic, container class. This authors of STL library did by defining List class inside #include <list>. It is a template, where each item of the list will contain our type T plus additional data needed – most likely pointer to the next and previous item.

struct MyStructure {
int MyNumber;
};
std::list<MyStructure> list;
list.push_back(MyStructure{3});

In other words, our structure is contained inside one that is defined internally by STL. After resolving template, it may look somewhat like this:

struct STL_ListItem {
STL_ListItem *Prev, *Next;
MyStructure UserData;
};

What if we want to do the opposite – to contain “utility” pointers needed to implement the list inside our custom structure? Maybe we have a structure already defined and cannot change it or maybe we want each item to be a member of two different lists, e.g. sorted by different criteria, and so to contain two pairs of previous-next pointers? A definition of such structure is easy to imagine, but can we still implement some generic class of a list to hide all the complex logic of inserting and removing elements, which would work on our own structure?

struct MyStructure {
int MyNumber = 0;
MyStructure *Prev = nullptr, *Next = nullptr;
};

If we could do that, such data structure could be called an “intrusive linked list”, just like an “intrusive smart pointer” is a smart pointer which keeps reference counter inside the pointed object. Actually, all that our IntrusiveLinkedList class needs to work with our custom item structure, besides the type itself, is a way to access the pointer to the previous and next element. I came up with an idea to provide this access using a technique called “type traits” – a separate structure that exposes specific interface to deliver information on some other type. In our case, it is to read (for const pointer) or access by reference (for non-const pointer) the previous and next pointer.

The traits structure for MyStructure may look like this:

struct MyStructureTypeTraits {
typedef MyStructure ItemType;
static ItemType* GetPrev(const ItemType* item) { return item->Prev; }
static ItemType* GetNext(const ItemType* item) { return item->Next; }
static ItemType*& AccessPrev(ItemType* item) { return item->Prev; }
static ItemType*& AccessNext(ItemType* item) { return item->Next; }
};

By having this, we can implement a class IntrusiveLinkedList<ItemTypeTraits> that will hold a pointer to the first and last item on the list and be able to insert, remove, and do other operations on the list, using a custom structure of an item, with custom pointers to previous and next item inside.

IntrusiveLinkedList<MyStructureTypeTraits> list;

list.PushBack(new MyStructure{1});
list.PushBack(new MyStructure{2});

for(MyStructure* i = list.Front(); i; i = list.GetNext(i))
printf("%d\n", i->MyNumber); // prints 1, 2

while(!list.IsEmpty())
delete list.PopBack();

I know this is nothing special, there are probably many such implementations on the Internet already, but I am happy with the result as it fulfilled my specific need elegantly.

To see the full implementation of my IntrusiveLinkedList class, go to D3D12MemAlloc.cpp file in D3D12 Memory Allocator library. One caveat is that the class doesn't allocate or free memory for the list items – this must be done by the user.

Comments | #algorithms #c++ Share

# Book Review: C++ Lambda Story

Wed
13
Jan 2021

C++ Lambda Story book

Courtesy its author BartÅ‚omiej Filipek, I was given an opportunity to read a new book “C++ Lambda Story”. Here is my review.

A book with 149 pages would be too short to teach entire C++, even in its basics, but this one is about a specific topic, just one feature of the language – lambda expressions. For this, it may seem like even too many, depending on how deeply the author goes into the details. To find out, I read the book over 3 evenings.

The book starts from the very beginning – the description of the problem in C++98/03, where lambdas were not available in the language, so we had to write functors - structs or classes with overloaded operator(). Then he moves on to describing lambdas as introduced in C++11, their syntax and all the features. Every feature described is accompanied by a short and clear example. These examples also have links to the same code available in online compilers like Wandbox, Compiler Explorer, or Coliru.

In the next chapters, the author describes what has been added to lambdas in new language revisions – C++14, C++17, C++20, and how other new features introduced to the language interact with lambdas – e.g. consteval and concepts from C++20.

Not only features of lambda expressions are described but also some quirks that every programmer should know. What if a lambda outlives the scope where it was created? What may happen when it is called on multiple threads in parallel? The book answers all these questions, illustrating each with a short, yet complete example.

Sometimes the author describes tricks that may seem too sophisticated. It turns out you can make a recursive call of your lambda, despite not directly supported, by defining a helper generic lambda inside your lambda. You can also derive your class from the implicit class defined by a lambda, or many of them, to have your operator() overloaded for different parameter types.

Your tolerance to such tricks depends on whether you are a proponent of “modern C++” and using all its features, or you prefer simple code, more like “C with classes”. Nonetheless, lambda expressions by themselves are a great language feature, useful in many cases. The book mentions some of these cases, as it ends with a chapter “Top Five Advantages of C++ Lambda Expressions”.

Overall, I like the book a lot. It describes this specific topic of lambda expressions in C++ comprehensively, but still in a concise and clear way. I recommend it to every C++ programmer. Because it is not very long, you shouldn’t hesitate with reading it like it was a new project you need to find time for. You should rather treat it like an additional, valuable learning resource, as if you read several blog articles or watched some YouTube videos about a topic of your interest.

You can buy the book on Leanpub. I also recommend visiting authors blog: C++ Stories (new blog converted from bfilipek.com). See also my review of his previous book: “C++17 in Detail”. There is a discount for “C++ Lambda Story” ($5.99 instead of $8.99) here, as well as for both books ($19.99 instead of $23.99) here - valid until the end of February 2021.

Comments | #books #c++ Share

Pages: 1 2 3 ... 19 >

[Download] [Dropbox] [pub] [Mirror] [Privacy policy]
Copyright © 2004-2024