Qt 6.5.3 Released

We have released Qt 6.5.3 today. As a patch release, Qt 6.5.3 does not introduce any new features but contains more than 500 bug fixes, security updates, and other improvements to the top of the Qt 6.5.2 release. See more information about the most important changes and bug fixes from Qt 6.5.3 release note.

Pitfalls of lambda capture initialization

Recently, I’ve stumbled across some behavior of C++ lambda captures that has, initially, made absolutely no sense to me. Apparently, I wasn’t alone with this, because it has resulted in a memory leak in QtFuture::whenAll() and QtFuture::whenAny() (now fixed; more on that further down).

I find the corner cases of C++ quite interesting, so I wanted to share this. Luckily, we can discuss this without getting knee-deep into the internals of QtFuture. So, without further ado:

Time for an example

Consider this (godbolt):

#include <iostream>
#include <functional>
#include <memory>
#include <cassert>
#include <vector>

struct Job
{
    template<class T>
    Job(T &&func) : func(std::forward<T>(func)) {}

    void run() { func(); hasRun = true; }

    std::function<void()> func;
    bool hasRun = false;
};

std::vector<Job> jobs;

template<class T>
void enqueueJob(T &&func)
{
    jobs.emplace_back([func=std::forward<T>(func)]() mutable {
        std::cout << "Starting job..." << std::endl;
        // Move func to ensure that it is destroyed after running
        auto fn = std::move(func);
        fn();
        std::cout << "Job finished." << std::endl;
    });
}

int main()
{
    struct Data {};
    std::weak_ptr<Data> observer;
    {
        auto context = std::make_shared<Data>();
        observer = context;
        enqueueJob([context] {
            std::cout << "Running..." << std::endl;
        });
    }
    for (auto &job : jobs) {
        job.run();
    }
    assert((observer.use_count() == 0) 
                && "There's still shared data left!");
}

Output:

Starting job...
Running...
Job finished.

The code is fairly straight forward. There’s a list of jobs to which we can be append with enqueueJob(). enqueueJob() wraps the passed callable with some debug output and ensures that it is destroyed after calling it. The Job objects themselves are kept around a little longer; we can imagine doing something with them, even though the jobs have already been run.
In main(), we enqueue a job that captures some shared state Data, run all jobs, and finally assert that the shared Data has been destroyed. So far, so good.

Now you might have some issues with the code. Apart from the structure, which, arguably, is a little forced, you might think “context is never modified, so it should be const!”. And you’re right, that would be better. So let’s change it (godbolt):

--- old
+++ new
@@ -34,7 +34,7 @@
     struct Data {};
     std::weak_ptr<Data> observer;
     {
-        auto context = std::make_shared<Data>();
+        const auto context = std::make_shared<Data>();
         observer = context;
         enqueueJob([context] {
             std::cout << "Running..." << std::endl;

Looks like a trivial change, right? But when we run it, the assertion fails now!

int main(): Assertion `(observer.use_count() == 0) && "There's still shared data left!"' failed.

How can this be? We’ve just declared a variable const that isn’t even used once! This does not seem to make any sense.
But it gets better: we can fix this by adding what looks like a no-op (godbolt):

--- old
+++ new
@@ -34,9 +34,9 @@
     struct Data {};
     std::weak_ptr<Data> observer;
     {
-        auto context = std::make_shared<Data>();
+        const auto context = std::make_shared<Data>();
         observer = context;
-        enqueueJob([context] {
+        enqueueJob([context=context] {
             std::cout << "Running..." << std::endl;
         });
     }

Wait, what? We just have to tell the compiler that we really want to capture context by the name context – and then it will correctly destroy the shared data? Would this be an application for the really keyword? Whatever it is, it works; you can check it on godbolt yourself.

When I first stumbled across this behavior, I just couldn’t wrap my head around it. I was about to think “compiler bug”, as unlikely as that may be. But GCC and Clang both behave like this, so it’s pretty much guaranteed not to be a compiler bug.

So, after combing through the interwebs, I’ve found this StackOverflow answer that gives the right hint: [context] is not the same as [context=context]! The latter drops cv qualifiers while the former does not! Quoting cppreference.com:

Those data members that correspond to captures without initializers are direct-initialized when the lambda-expression is evaluated. Those that correspond to captures with initializers are initialized as the initializer requires (could be copy- or direct-initialization). If an array is captured, array elements are direct-initialized in increasing index order. The order in which the data members are initialized is the order in which they are declared (which is unspecified).

https://en.cppreference.com/w/cpp/language/lambda

So [context] will direct-initialize the corresponding data member, whereas [context=context] (in this case) does copy-initialization! In terms of code this means:

  • [context] is equivalent to decltype(context) captured_context{context};, i.e. const std::shared_ptr<Data> captured_context{context};
  • [context=context] is equivalent to auto capture_context = context;, i.e. std::shared_ptr<Data> captured_context = context;

Good, so writing [context=context] actually drops the const qualifier on the captured variable! Thus, for the lambda, it is equivalent to not having written it in the first place and using direct-initialization.

But why does this even matter? Why do we leak references to the shared_ptr<Data> if the captured variable is const? We only ever std::move() or std::forward() the lambda, right up to the place where we invoke it. After that, it goes out of scope, and all captures should be destroyed as well. Right?

Nearly. Let’s think about the compiler generates for us when we write a lambda. For the direct-initialization capture (i.e. [context]() {}), the compiler roughly generates something like this:

struct lambda
{
    const std::shared_ptr<Data> context;
    // ...
};

This is what we want to to std::move() around. But it contains a const data member, and that cannot be moved from (it’s const after all)! So even with std::move(), there’s still a part of the lambda that lingers, keeping a reference to context. In the example above, the lingering part is in func, the capture of the wrapper lambda created in enqueueJob(). We move from func to ensure that all captures are destroyed when the it goes out of scope. But for the const std::shared_ptr<Data> context, which is hidden inside func, this does not work. It keeps holding the reference. The wrapper lambda itself would have to be destroyed for the reference count to drop to zero.
However, we keep the already-finished jobs around, so this never happens. The assertion fails.

How does this matter for Qt?

QtFuture::whenAll() and whenAny() create a shared_ptr to a Context struct and capture that in two lambdas used as continuations on a QFuture. Upon completion, the Context stores a reference to the QFuture. Similar to what we have seen above, continuations attached to QFuture are also wrapped by another lambda before being stored. When invoked, the “inner” lambda is supposed to be destroyed, while the outer (wrapper) one is kept alive.

In contrast to our example, the QFuture situation had created an actual memory leak, though (QTBUG-116731): The “inner” continuation references the Context, which references the QFuture, which again references the continuation lambda, referencing the Context. The “inner” continuation could not be std::move()d and destroyed after invocation, because the std::shared_ptr data member was const. This had created a reference cycle, leaking memory. I’ve also cooked this more complex case down to a small example (godbolt).

The patch for all of this is very small. As in the example, it simply consists of making the capture [context=context]. It’s included in the upcoming Qt 6.6.0.

Bottom line

I seriously didn’t expect there to be these differences in initialization of by-value lambda captures. Why doesn’t [context] alone also do direct- or copy-initialization, i.e. be exactly the same as [context=context]? That would be the sane thing to do, I think. I guess there is some reasoning for this; but I couldn’t find it (yet). It probably also doesn’t make a difference in the vast majority of cases.

In any case, I liked hunting this one down and getting to know another one of those dark corners of the C++ spec. So it’s not all bad 😉.

GammaRay 3.0.0 is released

KDAB has released GammaRay 3.0.0.

GammaRay is a software introspection tool for Qt applications developed by KDAB for internal use, and now shared with the developer community on GitHub.

This release is a special one, as we have now added Qt 6 support.

An overview of changes since the last release

Things we have added or improved

  • Support for Qt6
  • An option to allow capturing HTTP response body in Network operation.
  • You can now ‘favorite’ Objects via context menu. A favorite object appears in a separate item view above the main itemview
  • Support for modifying QMargins in properties
  • You can now zoom with a mouse wheel in Signals view
  • When you filter for an object, the tree will now expand automatically and select the object
  • When an object is re-parented, it no longer crashes
  • You get much improved performance
    • in signals view
    • when a target application triggers massive object destructions or constructions

Things we took away

  • The KDAB commercial license.
  • The 3D Widget Inspector View.
  • The experimental VTK-based Object Visualizer.

Download GammaRay and see the full change log at https://github.com/KDAB/GammaRay/releases/tag/v3.0.0.

Find out more about KDAB’s Qt tools.

Videos
About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post GammaRay 3.0.0 is released appeared first on KDAB.

Unlock the Power of Qt with Felgo's Qt Explained Series on YouTube

With powerful SDK components and unique tools tailored to the daily needs of developers, Felgo’s mission is to enable developers to work efficiently and create better Qt applications. As a Qt Technology and Service Partner, Felgo supports developers and businesses in bringing ideas to life and reaching their goals.  

To make Qt development more accessible and strengthen the community, we are now thrilled to announce a brand-new video series: Qt Explained. It is designed to empower developers of all levels with the magic of Qt:

KDAB at CppCon October 1-6th

CppCon is the annual, week-long face-to-face gathering for the entire C++ community – the biggest C++ event in the world. This year, CppCon takes place in Aurora, Colorado, near the Denver airport, and including multiple diverse tracks, the conference will appeal to anyone from C++ novices to experts.

At CppCon, you can expect lots of interesting talks and panels, presentations by the C++ community, lightning talks, and evening events.

KDAB’s Nate Rogers and Matt Aber will be there to welcome you all at the KDAB table on October 2nd-3rd. After that, feel free to grab them whenever you see them strolling around the facility. Maybe you want an introduction to what it’s like to work at KDAB, or perhaps what we do in and for the C++ community?

We hope to see you there!

KDAB at CppCon

 

The post KDAB at CppCon October 1-6th appeared first on KDAB.

Qt Journey– Revolutionizing networking security

by Emilia Valkonen-Damjanovic (Qt Blog)

In this series, we'll bring you various career stories from people working with Qt. Today, I'm interviewing Jeremy Tubongbanua, a software engineering student and software engineer at Atsign, based in Ontario, Canada.

Generic Struct Handling is Coming to Qt OPC UA

Generic Struct Handling is Coming to Qt OPC UA

OPC UA servers often use structured data types, for example when they are implementing a companion specification or exposing custom structured data types from a PLC program. Up to now, Qt OPC UA was just returning a binary blob when reading such a value and the decoding was left entirely to the user. Since OPC UA 1.04, there is a standardized way for a server to expose the data type description for custom data types. We have extended Qt OPC UA to use this information to make it much easier to encode and decode custom data types. The following article introduces the new API.

Continue reading Generic Struct Handling is Coming to Qt OPC UA at basysKom.

Introducing the RiveQtQuickPlugin – Powerful Animations For Your QtQuick Applications

Rive is a popular tool for vector animations. While the editor itself is a closed source commercial product, there are FOSS implementations for the player runtime. basysKom has developed a QtQuick integration based on the rive-cpp library. This article introduces the project and its current state.

Continue reading Introducing the RiveQtQuickPlugin – Powerful Animations For Your QtQuick Applications at basysKom.

How Trademarks Affect Open Source Software — How do trademarks relate to copyrights and what are the implications for open source software

As previously discussed, a central concept of open source licenses is that the author of a given program grants anyone permission to use, modify, or redistribute their code without seeking specific permission first. But these licenses generally cover the copyrights to the underlying code and not any trademarks that may be used by the original author in connection with their work.

A trademark is basically a word, phrase, symbol, or design--or any combination of those elements--that identifies a unique product or service in commerce. For example, "Python" is a registered trademark of the Python Software Foundation (PSF). The famous blue-and-yellow "intertwined snakes" Python logo is also a PSF trademark.

So how exactly do trademarks work? And as a developer, what can you do--and not do--with someone else's trademarks in your own work?

Trademarks Are Not Copyrights

To the layperson, it might seem like trademarks are just a method by which someone can copyright a particular word or phrase. In fact, copyright and trademark are distinct forms of intellectual property. A copyright applies to a created work, such as a book or a software program, that is original and exists in some fixed form. The purpose of copyright to is to enable the author to exercise exclusive control over the right to reproduce or distribute their work for a specified period of time.

A trademark, in contrast, is primarily about protecting consumers from harm. A trademark identifies the source of a good or service. This helps guard against potential counterfeiting or fraud while affording the trademark holder legal protection for their brand. But the trademark holder does not "own" the trademarked word or phrase. Rather, they exercise a limited right to use the trademark in connection with the sale of their product or service in a given area.

Trademarks can be used in connection with a variety of products within the same brand. The PSF uses its Python mark, for instance, not just to refer to the programming language itself but also merchandise such as T-shirts and hats. But the PSF could not enforce its Python mark in an area of commerce where it does not actually produce any products.

Like copyright, a trademark holder can register their mark with the government, although it is not required. In the United States, the U.S. Patent and Trademark Office (USPTO) manages trademark registrations at the federal level. A registered trademark is indicated by an "R" with a circle around it (®), while an unregistered trademark is indicated with the letters "TM" (™). As with copyright, registering a trademark provides certain legal benefits, such as a presumption that the mark is valid and belongs to the registrant.

When Do You Need a Software Trademark Holder's Approval to Use Their Name?

Another key difference between copyright and trademark is enforcement. With copyright, an author can be fairly lax in their assertion of rights without losing them. Indeed, open source licensing is essentially built around the notion that most developers do not want to spend their time policing how other people use their code. Yet the license itself still retains copyright in the event the author feels compelled to take legal action against infringement.

With a trademark, however, the holder needs to be more proactive in asserting their rights. The reason for this is that unlike copyright, a trademark can last indefinitely, provided it continues to be actively used and defended by the holder. The more passive the holder is in defending their rights, the more likely that the USPTO or a judge may find the trademark is no longer in active use.

For this reason, many organizations that rely on trademarks will publish a written policy governing their acceptable use. The PSF has a very detailed trademark usage policy, which provides a good model for how enforcement works in practice. Fortunately for developers, the PSF policy makes it clear that the organization wants the "Python" mark and logo "to be used with minimal restriction to refer to the Python programming language."

To be clear, even without this policy, there are many uses of the "Python" trademark allowed by U.S. law (and the laws of other nations) with respect to "fair use." There are basically two kinds of fair use: nominative and descriptive. Nominative fair use simply means you are referring to the trademarked good or service. So if you write a program in Python, you are allowed to refer to the "Python" trademark without asking the PSF's permission first.

Descriptive fair use means you are using the trademark to describe some other product or service. This tends to come up when you are comparing one product with another. For instance, if you write an article comparing the benefits and drawbacks of Python with Rust, that would involve descriptive fair use. Again, you don't need permission to do this.

So when do you need a trademark holder's approval? The PSF's own policy states approval is necessary for any "commercial use" of the "Python" trademark to describe another product or company. In other words, you don't need the PSF's permission to describe your own program as being written in Python, or even to describe yourself as a Python developer. But you would need the PSF's permission to name your company something like "Python Software, Inc."

But what if you wanted to make or sell merchandise with the Python logo? Again, the PSF's policy permits anyone to use the logo without permission for non-commercial purposes. It's okay to make a Python-logo shirt for yourself and wear it at PyCon. But if you wanted to sell those shirts as PyCon, you would need a license from the PSF and potentially pay royalties on any sales.

Four Things to Know About Software and Trademarks

If you are developing software, you need to be aware of potential trademark issues. A qualified intellectual property attorney can answer specific questions for your situation. But speaking in general terms, here are four things to keep in mind:

  1. If you plan to use the name of any existing company or project in describing your own software, make sure to check and see if the trademark holders have a published policy regarding usage like the PSF.
  2. When selecting a name for your own project, you can check the U.S. Patent and Trademark Office's trademark database to see if the same or similar name is already in use with respect to software.
  3. Even if a trademark is not formally registered with the USPTO, that does not mean the trademark is not still legally protected.
  4. An open source license like the GPL, MIT, or BSD licenses do not convey any rights with respect to trademarks. Never assume that you can use the name of another software project in your own commercial application just because the underlying code is freely licensed.

Customizing Your Tkinter App's Windows — Make Your Tkinter App's Windows Have Different Looks

Different desktop applications have different design requirements. If you look at the applications on your computer, you will see that most of them have different window designs. Some applications, like games, run in full-screen mode. Utility applications, like calculators, run in fixed-size mode with the maximize or minimize button disabled.

Forms or windows have different appearances based on their app's requirements. As you create your own Tkinter applications, you might also want to have windows without a title bar, windows that can't be resized, windows that are zoomed, and even windows that show some level of transparency.

In this tutorial, you will learn some Tkinter tricks and techniques that you can use to customize your applications' windows.

Getting to Know Window Configurations

In Tkinter, a window configuration is either a setting or an attribute that you can use to specify a property of that window. These properties may include the window's width, height, position, transparency, title bar, background color, and more.

These configurations allow you to tweak and customize the look and feel of your application's windows and forms so that they look modern and nice in the eyes of your app's users.

For example, let's say you want to create a game, and you need to remove the main window's title bar. Keep reading to learn how to do this in Tkinter.

Creating a Simple Window in Tkinter

To kick things off, let's create a minimal Tkinter app that will work as our starting point for learning how to remove a window's title bar. Here's the required code:

python
from tkinter import Tk

# Create the app's main window
root = Tk()
root.title("Window With a Title Bar")
root.geometry("400x300+300+120")

# Run the app's main loop
root.mainloop()

Here, we import Tk from tkinter. Then we create the app's main window, root, by instantiating Tk. Next, we give our window a title and geometry using the title() and geometry() methods, respectively.

Go ahead and save this code to a file called app.py. Then run the file from your command line. The output will look something like this:

A Tkinter app showing a window with the default title bar A Tkinter app showing a window with the default title bar

On your screen, you'll get a regular Tkinter window with the title bar and the decoration provided by your current operating system.

Removing the Window's Title Bar in Tkinter

Tkinter makes it possible for you to remove the system-provided title bar of your app's main window. This tweak is handy when building a custom GUI that doesn't use the default window decorations.

The default title bar highlighted on our example window The default title bar highlighted on our example window

In the image above, the red border highlights the window's title bar. That's what we want to remove. To do that, we can use a method called overrideredirect(). If we pass True as an argument to this method, then we'll get a frameless window.

Go ahead and update your code. Make it look something like this:

python
from tkinter import Tk

# Create the app's main window
root = Tk()
root.geometry("400x300+300+120")

# Removes the window's title bar
root.overrideredirect(True)

# Run the app's main loop
root.mainloop()

By calling root.overrideredirect(True), we tell the window manager (which manages windows on your desktop) not to wrap the window in the usual window decorations. If you run the app again, then you will get the following output:

A Tkinter app showing a window without title bar A Tkinter app showing a window without title bar

You have successfully created a Tkinter app with a window that doesn't have the standard window decorations from your desktop window manager.

Because the app's window has no close button, you must press Alt+F4 to close the window and terminate the app.

Disabling the Window's Maximize/Minimize Button

There are some situations where we would want to have a window with a title bar but with a fixed size. That would be the case with a calculator application, for example. To do that, we can use the resizable() method, which takes two boolean arguments:

  1. width specifies whether the window can be horizontally resized.
  2. height specifies whether the window can be vertically resized.

If you pass False for both arguments, you will disable resizing in both directions. Below we've modified the code for our simple Tkinter app, preventing users from resizing the main window:

python
from tkinter import Tk

# Create the app's main window
root = Tk()
root.title("Fixed Size Window")
root.geometry("400x300+300+120")

# Disable the window's resizing capability
root.resizable(False, False)

# Run the app's main loop
root.mainloop()

In this example, the code calls resizable() with its width and height argument set to False. This call makes the window unresizable. If you run this app, then you'll get the output shown below:

A Tkinter app showing a fixed size window A Tkinter app showing a fixed size window

Try to resize this window by dragging any of its borders, and you'll find that you can't resize it in either direction. You will also discover that the maximize/minimize buttons are now also disabled, preventing you from resizing the window in this way.

Displaying the App's Window in Zoomed Mode

Tkinter also allows you to display an app's window in zoomed mode. In zoomed mode, your application's window will display in fullscreen. A common scenario where this mode comes in handy is when you want to provide an immersive experience to your users.

On Windows and macOS, the method for displaying the app's window in zoomed mode is state(). You can pass the "zoomed" string as an argument to this method to get the desired result. The code for that will look like below:

python
from tkinter import Tk

# Create the app's main window
root = Tk()
root.title("Zoomed Window")
root.geometry("400x300+300+120")

# Set the window to a zoomed mode
root.state("zoomed")

# Run the app's main loop
root.mainloop()

The line root.state("zoomed") makes the window display already zoomed on both Windows and macOS. If you are on Linux, then use root.attributes("-zoomed", True) instead. The app's window looks something like this:

A Tkinter app showing a zoomed window A Tkinter app showing a zoomed window

In this screenshot, you can see that the application's main window occupies the entire screen, which gives you a larger working area.

Changing the Window's Transparency Level

What if you wanted to change the transparency of your app's main window? You can do this using the attributes() method. To set the transparency, you provide two arguments: first the string "-alpha", then a floating-point number that ranges from 0.0 to 1.0. A value of 0.0 represents the highest transparency level (full transparency, your window will become invisible), while a value of 1.0 value represents the lowest level (no transparency).

Let's create a window with a 0.6 transparency level:

python
from tkinter import Tk

# Create the app's main window
root = Tk()
root.title("0.6 Transparency Window")
root.geometry("400x300+300+120")

# Set the -alpha value to 0.6
root.attributes("-alpha", 0.6)

# Run the app's main loop
root.mainloop()

In this example, we set the "-alpha" attribute to 0.6. This tweak generates a window that looks something like this:

A Tkinter app showing a transparent window A Tkinter app showing a transparent window

Your app's main window is now 60% transparent. Isn't that cool? Do you have any creative ideas for your next application?

Conclusion

In this tutorial, you've gone through the process of customizing the root window of a Tkinter application using several different methods, attributes, and properties. You've learned how to remove the title bar of a window, make a window have a fixed size, display a window in zoomed mode, and more.

KDAB Training Day before Qt World Summit 2023

The KDAB Training Day will be back in Berlin on November 27th this year, right before the annual Qt World Summit, which will happen November 28-29th. KDAB Training Day will be held at the H4 Hotel Berlin Alexanderplatz, just down the road from the bcc Berlin Congress Centre where The Qt World Summit takes place, starting the following day. Ideal to take part in both events!

KDAB is well-known for its quality training courses around Qt/QML, Modern C++, Debugging and Profiling, OpenGL, and other topics relevant to Qt developers. All courses provided by KDAB at the Training Day include central parts of our regular 3- to 4-day courses that are available as scheduled training or customized on-site training.

Choosing a compact, learning-rich one-day course, lets you experience the quality and effectiveness of KDAB’s usual training offerings.

At this year’s KDAB Training Day, you get to choose from the following topics:

  • What’s new in C++23, trained by Giuseppe D’Angelo
  • QML Application Architecture, trained by André Somers
  • Profiling on Linux, trained by Milian Wolff
  • Porting to Qt 6, trained by Nicolas Fella

And for the first time in collaboration with the Rust experts from Ferrous Systems:

  • A taste of Rust (with a drop of Qt), trained by Florian Gilcher

You can find more information about each training by clicking here.

Get your ticket today! We have limited seats available for each training. So you should not wait too long if you want to take part in a specific course. Tickets include access to one training course, training material, lunch buffet, beverages, and coffee break.

Note: there is no combo ticket for the KDAB Training Day and Qt World Summit like in previous years. Please, make sure to book your ticket for Qt World Summit separately here, if you want to attend.

We are looking forward to seeing you in Berlin!

The post KDAB Training Day before Qt World Summit 2023 appeared first on KDAB.

Release 4.1.0: Build Qt 6 Apps for WebAssembly, Integrate Firebase Analytics, Gaming Components and SpeechToText

The Felgo 4.1.0 update allows you to build Qt 6 and Felgo 4 apps for WebAssembly. As a replacement for Google Analytics, which will shut down at the end of June, you can now use the new Firebase Analytics Plugin

In addition, the latest release includes support for Felgo Gaming Components, adds SpeechToText for iOS and Android, and improves the Felgo Live connection process.

Felgo 4.1.0 comes as a free update for all Felgo developers.

How to become friends with qmllint

As Qt developers, we produce many lines of QML code every day. Of course, we are all aware of the importance of maintainable, well-organised pieces. We know the best practices, and we try to follow them every time we add new code to the existing base. However, as our project becomes more complex, it can be difficult to keep track of all the best practices and ensure that the code meets the required standards. That's one of the reasons why you should consider qmllint as a candidate for your new friend.

In this article, I want to show you how I work with qmllint and how you can integrate qmllint checks into your daily tasks.

The post How to become friends with qmllint appeared first on Spyrosoft.

Object Lifetime

Last time we discussed Value Semantics. However, I missed one topic that is super important for a better understanding of basic building blocks of C++. Today, we are going to talk about an object. Without further ado, let’s dive deeper!

Object

What is an object? According to the C++ standard, part 3.9.8 under the name of [basic.types]

 An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not a void type.

Now is int i an object? Yes.

Is void* p an object? Still yes, because pointers are types themselves, they are not references.

As we said, references are not types, but what if we declare something like struct S{ int& ref;}; would that be an object type? Put your answers down below, I’d like to see your opinion on this. 🙂

Lifetime

When we talk about objects, the one idea that always follows is lifetime.

Lifetime is a period in which an object is fully initialized. It begins with

  • storage with the proper alignment,
  • obtaining size for type T, and
  • if the object has non-trivial initialization, its initialization is complete.

The lifetime of an object of type T ends when:

  • T is a class type with a non-trivial destructor,
  • the destructor call starts, or
  • the storage which the object occupies is reused or released

This is an extraction from [basic.life] paragraph of C++ standard. In easy words, complex objects live from the end of the constructor, until the beginning of a destructor, and the trivial objects, like int live from their storage allocation, until the end of the scope. Seems pretty easy, right?

So, if you see something like:

auto& GetData(){
    const int i = 5;
    return i;
}

you’d probably say that there is something fishy about it, and you’d be correct. Although i is returned from a function, notice the auto& here. It says that we’re actually returning a reference to a local variable. After the function returns, the stack is cleaned. Hence, you invoke a good old UB.

But what if we change the function to return a string instead of an int?

auto* GetData(){
    const char* i = "Hello";
    return i;
}

Now we get a normal “Hello” string from a function. This is due to a simple rule of a language, that states that all strings are valid throughout the entire program because they have a static storage.

But let’s change the story again:

auto* GetData(){
    const char[] i = "Hello";
    return i;
}

Notice that I’ve changed only one character and suddenly everything breaks. The behavior is undefined, everything crashes and burns. What happened here? We actually initialized the array with a copy of static data and returned a pointer to it.

Strings live longer, than you think they will, except when they don’t – Jason Turner @ CppCon 2018

Complex objects and their lifetimes

As we already discussed, lifetime of a complex object begins with a constructor and ends with a destructor. Although there are objects that have implicit destructors and constructors, there still may be things that may surprise you.

Firstly, the lifetime begins with the ending of the constructor execution.

That means a lot. Let’s have a look at an example:

struct S {
S(){}
~S(){}
};

int main(){S s;}

I think everyone will agree that the call order is constructor, and then destructor. But what if I present exceptions to the equation? What if S() may throw?

Well, the answer is: the destructor does not start! Why? Because the lifetime of an object did not start, since the constructor didn’t finish its execution.

Here lies a menace:

struct S {
S(int a){
    a = new MyWidgetNothrow(a);
    b = new MyWidgetThrows(a);
}
~S(){
   delete a;
   delete b;
}

MyWidget* a;
MyWidget* b;
};

int main(){S s;}

Are widgets deleted with the unwinding if the second object throws? No, the memory will leak. Hence, this is why the usage of new operator is a bad code smell and we should avoid it whenever possible. Here is an example on Godbolt.

However, a good thing to mention is that the stack unwinder actually destroys all initialized objects from within the class on throw. So, the pointer being an object type is trivially destroyed, but not the memory behind it.

What can we do to make this code work? Delegating constructors comes into play!

struct S {
S() = default;
S(int a):S{} {
a = new MyWidgetNothrow;
b = new MyWidgetThrows;
}
~S(){
delete a;
delete b;
}

MyWidget* a = nullptr;
MyWidget* b = nullptr;
};

int main(){S s;}

Here, the delegation constructor finished its execution, hence the object is alive, and the destructor finishes successfully on throw. This information brings us to the point of smart pointers. Since in many cases you can’t have an additional constructor to delegate to, or even a constructor at all, you need smart pointers to have robust code and ensure correct ownership of the objects.

Conclusion

Today we have touched a very basic yet complex topic of object lifetimes. Of course, there is a lot more to say and we will continue basics in the next post tackling move semantics with lifetime implications and finishing with a sprinkle of ownership semantics of smart pointers.

I hope you enjoyed the read. There is a plan for a video series that tackles modern C++ 20/23 topics and use the concepts in practice. Stay tuned for the announcement. 🙂

Be careful when returning references and wrapper objects from a function, and use new as rarely as possible! Enjoy object lifetime!

 

Sincerely,

Ilya “Agrael” Doroshenko

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Object Lifetime appeared first on KDAB.