Skip to content

Wednesday, 18 September 2024

This year was my first Akademy, and I was thrilled to be able to attend in person. Even better, some veterans said it was the best Akademy so far. It was great to see some people I had met in April and to meet new people. I arrived on Friday, 6th Sept and left the following Friday. I very much enjoyed staying in the lovely town of Würzburg and doing a day tour of Rothenberg. Now that I've caught up on sleep (the jet lag, it is real), it's time to write about it.

As described in the Akademy 2024 report, the focus this year was resetting priorities, refocusing goals and reorganizing projects. Since I had recently become a more active contributor to KDE, I was keen to learn about the direction things will take over the next year. It was also exciting to see the new design direction in Union and Plasma Next!

A Personal Note

Speaking of goals, a personal one I've striven toward in my career is to contribute to something that improves the world, even if indirectly. It's not something I've always been able to do. It feels good to be able to work with a project that is open source, and is working to make the computing world more sustainable.

I'd also like to recognize all the wonderful, welcoming folks that make Akademy such a great conference. I've been to a few other tech conferences and events, with varying atmospheres and attitudes. I can say that people at Akademy are so helpful, and so nice - it made being among a lot of new faces in a foreign country a truly great experience.

The Conference

The keynote had powerful information about the impacts of improper tech disposal and what we can do to improve the situation. This highlighted the importance of the KDE Eco project, which aims to help to reduce e-waste and make our projects more sustainable. Their new Opt Green initiative is going to take concrete steps toward this.

Some of the talks I attended:

  • KDE Goals - one talk about the last 2 years of goals and another revealing the new goals.
  • "Adapt or Die" - how containerized packaging affects KDE projects.
  • Union and styling in KDE's future.
  • Banana OS KDE Linux - why it's being developed and some technical planning.
  • Getting Them Early: Teaching Pupils About The Environmental Benefits Of FOSS This strategy has been powerful for other projects (like Microsoft Windows, Google Chromebooks, Java), so I'm glad to see it for KDE.
    • Why and how to use KDE frameworks in non-KDE apps
    • KDE Apps Initiative
    • Cutting Gordian's "End-User Focus" vs. "Privacy" Knot - collecting useful user data while respecting privacy and security.
    • Plasma Next - Visual Design Evolution for Plasma
    • The Road to KDE Neon Core

BoF Sessions

Some of the BoF sessions I attended:

  • Decentralizing KUserFeedback
  • Streamlined Application Development Experience
  • Organizing the Plasma team, Plasma goals
  • Plasma "Next" initiative
  • Union: The future of styling in KDE
  • KWallet modern replacement
  • Video tutorial for BoF best practice (Kieryn roped me into this one)
  • Security Team BoF Notes

Thanks to everyone who made this year's Akademy such a wonderful experience. If anyone out there is thinking of attending next year, and can make it, I really recommend it. I'm hoping to be at Akademy 2025!

Contrary to popular belief, Akademy 2024 was not my first Akademy. KDE seems to keep tagged faces from Akademy group photos around, so I stumbled over some vintage pictures of me in 2006 (Glasgow) and 2007 (Dublin). At the time, I was an utter greenhorn with big dreams, vague plans, and a fair bit of social anxiety.

Dublin is where Aaron Seigo taught me how to eat with chopsticks. I continue to draw from that knowledge to this day.

And then I disappeared for 15 years, but now it's time for a new shot. This time around, I'm a little less green (rather starting to grey a little) and had a surprising amount of stuff to discuss with various KDE collaborators. Boy, is there no end of interesting people and discussion topics to be had at Akademy.

"Oh, you're the PowerDevil guy"

You're not wrong, I've been contributing to that for the past year. As such, one highlight for me was to meet KDE's hardware integration contractor Natalie Clarius in person and sync up on all things power-related.

Akademy's no-photo badge makes its wearer disappear from selfies. AI magic, perhaps?

Natalie presented a short talk and hosted a BoF session ("Birds of a Feather", a.k.a. workshop) about power management topics. We had a good crowd of developers in attendance, clearing up the direction of several outstanding items.

Power management in Plasma desktops is in decent shape overall. One of the bigger remaining topics is (re)storing battery charge limits across reboots, for laptops whose firmware doesn't remember those settings. There is a way forward that involves making use of the cross-desktop UPower service and its new charge limit extensions. This will give us the restoring feature for free, but we have to add some extra functionality to make sure that charge threshold customization remains possible for Plasma users after switching over.

We also looked at ways to put systems back to sleep that weren't supposed to wake up yet. Unintended wake-ups can happen e.g. when the laptop in your backpack catches a key press from the screen when it's squeezed against the keyboard. Or when one of those (conceptually neat) "Modern Standby" implementations on recent laptops are buggy. This will need a little more investigation, but we've got some ideas.

I talked to Bhushan Shah about power saving optimizations in Plasma Mobile. He is investigating a Linux kernel feature designed for mobile devices that saves power more aggressively, but needs support from KDE's power management infrastructure to make sure the phone will indeed wake up when it's meant to. If this can be integrated with KDE's power management service, we could improve battery runtimes for mobile devices and perhaps even for some laptops.

The friendly people from Slimbook dropped by to show off their range of Linux laptops, and unveiled their new Slimbook VI with KDE neon right there at the conference. Compared to some of their older laptops, build quality is improved leaps and bounds. Natalie and I grilled their BIOS engineer on topics such as power profiles, power consumption, and how to get each of their function keys show the corresponding OSD popup.

KDE Slimbook VI shortly after the big reveal

"I'm excited that your input goal was chosen"

Every two years, the KDE community picks three "Goals" to rally around until the next vote happens. This time, contributors were asked to form teams of "goal champions" so that the work of educating and rallying people does not fall on the shoulders of a single poor soul per goal.

So now we have eight poor souls who pledge to advance a total of three focus areas over the next two years. Supported by KDE e.V.'s new Goals Coordinator, Farid. There's a common thread around attracting developers, with Nicolas Fella and Nate Graham pushing for a "Streamlined Application Development Experience" and the KDE Promo team with a systematic recruitment initiative titled "KDE Needs You". And then there's this other thing, with a strict end user focus, briefly introduced on stage by guess who?

Yup. Hi! I'm the input guy now.

Turns out a lot of people in KDE are passionate about support for input devices, virtual keyboards and input methods. Gernot Schiller (a.k.a. duha) realized this and assembled a team consisting of himself, Joshua Goins (a.k.a. redstrate) as well as Yours Truly to apply as champions. The goal proposed that "We care about your Input" and the community's response is Yes, Yes We Do.

As soon as the new goals were announced, Akademy 2024 turned into an Input Goal Akademy for me. In addition to presenting the new goal on stage briefly, we also gathered in a BoF session to discuss the current state, future plans and enthusiastic volunteering assignments related to all things input. I also sat down with a number of input experts to learn more about everything. There is still much more I need to learn.

It's a sprawling topic with numerous tasks that we want to get done, ranging from multi-month projects to fixing lots of low-hanging fruit. This calls for a series of dedicated blog posts, so I'll go into more detail later.

Join us at #kde-input:kde.org on Matrix or watch this space (and Planet KDE in general) for further posts on what's going on with input handling in KDE.

Look at the brightness side

KWin hacker extraordinaire Xaver Hugl (a.k.a. zamundaaa) demoed some of his color magic on a standard SDR laptop display. Future KWin can play bright HDR videos in front of regular SDR desktop content. Accurate color transformations for both parts without special HDR hardware, that's pretty impressive. I thought that HDR needs dedicated hardware support, turns out I'm wrong, although better contrast and more brightness can still improve the experience.

I also got to talk to Xaver about touchpad gestures, learned about stalled attempts to support DDC/CI in the Linux kernel directly, and pestered him for a review to improve Plasma's D-Bus API for the new per-monitor brightness features. Also the XDC conference in Montreal, is happening in less than a month, featuring more of Xaver as well as loads of low-level graphics topics. Perhaps even input-related stuff. It's only a train ride from Toronto, maybe I'll drop by. Maybe not. Here's a medieval German town selfie.

Towering over the rooftops of Rothenburg ob der Tauber with Xaver, Jonathan Riddell, and two suspect KWin developers in the back

Thanks to the entire KWin gang for letting me crash their late-night hacking session and only throwing the last of us out at 2 AM after my D-Bus change got merged. Just in time for the Plasma 6.2 beta. I was dead tired on Thursday, totally worth it though.

Atomic KDE for users & developers

Plasma undoubtedly has some challenges ahead in order to bring all of its power and flexibility to an image-based, atomic OS with sandboxed apps (i.e. Flatpak/Snap). David Edmundson's talk emphasized that traditional plugins are not compatible with this new reality. We'll need to look into other ways of extending apps.

David Edmundson wildly speculating about the future

The good news is that lots of work is indeed happening to prepare KDE for this future. Baloo making use of thumbnailer binaries in addition to thumbnailer plugins. KRunner allowing D-Bus plugins in addition to shared libraries. Arjen Hiemstra's work-in-progress Union style being customizable through configuration rather than code. Heck, we even learned about a project called KDE Neon Core trying to make a Snap out of each and every piece of KDE software.

Going forward, it seems that there will be a more distinct line between Plasma as a desktop platform and KDE apps, communicating with each other through standardized extension points.

All of this infrastructure will come in handy if Harald Sitter's experimental atomic OS, KDE Linux (working title), is to become a success. Personally, I've long been hoping for a KDE-based system that I can recommend to my less technical family members. KDE Linux could eventually be that. Yes, Fedora Kinoite is also great.

KDE Linux: Useful to users, hardware partners, and... developers?

What took me by surprise about Harald's presentation was that it could be great even as a development platform for contributing to the Plasma desktop itself.

As a desktop developer, I simply can't run my Plasma development build in a container. Many functions interact with actual hardware so it needs to run right on the metal. On my current Arch system, I use a secondary user account with Plasma installed into that user's home directory. That way the system packages aren't getting modified - one does not want to mess with system packages.

But KDE Linux images contain the same system-wide build that I would make for myself. I can build an exact replacement with standard KDE tooling, perhaps a slight bit newer, and temporarily use it as system-wide replacement using systemd-sysext. I can revert whenever. KDE Linux includes all the development header files too, making it possible to build and replace just a single system component without building all the rest of KDE.

Different editions make it suitable for users anywhere between tested/stable (for family members) and bleeding edge (for myself as Plasma developer). Heck, perhaps we'll even be able to switch back and forth between different editions with little effort.

Needless to say, I'm really excited about the potential of KDE Linux. Even without considering how much work it can save for distro maintainers that won't have to combine outdated Ubuntu LTS packages with the latest KDE desktop.

Conclusion

There's so much else I've barely even touched on, like NLnet funding opportunities, quizzing Neal Gompa about KDE for Enterprise, Rust and Python binding efforts, Nicolas Fella being literally everywhere, Qt Contributor Summit, finding myself in a hostel room together with fellow KDE devs Carl & Kåre. But this blog post is already long enough. Read some of the other KDE blogs for more Akademy reports.

German bus stops have the nicest sunsets. Also rainbows!

Getting home took all day and jet lag isn't fun, but I've reasonably recovered to give another shot at bringing KDE software to the masses. You can too! Get involved, donate to KDE, or simply enjoy the ride and discuss this post on KDE Discuss.

Or don't. It's all good :)

Tuesday, 17 September 2024

Introduction

Update: intentionally controversial

With enough time past for feedback to have arrived in the past two years, I can now reveal that this original proposal was never intended as a final solution.

It was intentionally controversial, so more people – both lawyers and front-end developers – would complain and a proper discussion on the long-ignored topic would arise. That is why it had to be technically perfectly viable to launch as-is. Otherwise it would be too simple to brush off as not workable at all.

License information in source code is best stored in each file of the source code as a comment, if at all possible. That way the license metadata travels with the file even if it was copied from its original package/repository into a different one.

Client-side JavaScript, CSS and similar languages that make up a large chunk of the web are often concatenated, minified and even uglified in an attempt to make the website faster to load. In this process, most often, the comments get culled the first to reduce the number of characters that serve no function to the program code itself.

Problem

The problem therefore is that typically when JavaScript, CSS (or similar client-side1 code) is being built, it tends to lose not just comments that describe the code’s functionality, but also comments that carry licensing and copyright information. And since licenses (FOSS or not) typically require the text of the license and copyright notices2 to be kept with the code, such removal can be problematic.

Proposal

The goal is to preserve copyright and licensing information of web front-end code even after minification in such a way that it makes FOSS license compliance3 of web applications easier.

In addition, my proposal is intended to keep things:

  • as simple as possible;
  • as short as possible;
  • not introduce any new specifications, but rely on well-established standards; and
  • not require any additional tooling, but rely on what is already in common use.

Essentially, my suggestion is literally as simple as wrapping every .js, .css and similar (e.g. .ts, .scss, …) file with SPDX snippets tags, following the REUSE specification as follows:

At the very beginning of the file introduce an “important” comment block that starts the (SPDX) snippet and includes all the REUSE/SPDX tags that apply to this file, e.g.:

/*!
 * SPDX-SnippetBegin
 * SPDX-CopyrightText: © 2024 Hacke R. McRandom <hacker@mcrandom.example>
 * SPDX-LicenseIdentifier: MIT
 */

And at the very end of the file introduce another “important” comment block that simply closes the (SPDX) snippet:

/*! SPDX-SnippetEnd */

… and that is basically it!

How any why this works (in theory)

This results in e.g. a .js file that would look something like this:

/*!
 * SPDX-SnippetBegin
 * SPDX-CopyrightText: © 2024 Hacke R. McRandom <hacker@mcrandom.example>
 * SPDX-LicenseIdentifier: MIT OR Unlicense
 */

import half_of_npm

code_goes_here();

/*! SPDX-SnippetEnd */

and a .css file as follows:

/*!
 * SPDX-SnippetBegin
 * SPDX-CopyrightText: © 2020 Talha Mansoor <talha131@gmail.com>
 * SPDX-LicenseIdentifier: MIT
 */

}
pre {
  overflow: auto;
  white-space: pre;
  word-break: normal;
  word-wrap: normal;
  color: #ebdbb2; /* This is needed due to bug in Pygments. It does not wraps some part of the code of some lagauges, like reST. This is for fallback. */
}

/*! SPDX-SnippetEnd */

All JavaScript, CSS, TypeScript, Sass, etc. files would look like that.

Then on npm run build (or whatever build system you use) the minifier keeps those tags where they are, because ! is a common trigger to keep that comment when minifying.

So if all the files are tagged as such4, the minified barf5 you get, should include all the SPDX tags in order and in the right place, so you see which license/copyright starts and ends to apply in the gibberish.

And if it pulls stuff that does not use REUSE (snippets) yet, you will still be able to tell it apart6, since it will be the barf that’s between SPDX-SnippetEnd of the previous and SPDX-SnippetBegin of the next properly marked barf.

Is this really enough?

OK, so now we know the start and end of a source code file that ended up in the minified barf. But are the SPDX-SnippetCopyrightText and SPDX-License-Identifier enough?

I think so, yes.

If I chose to express my copyright notice using an SPDX tag – especially if I followed the format that pretty much all copyright laws prescribe – that should be no problem7.

The bigger question is whether communicating the license solely through the SPDX IDs8 is enough, since you would technically not be including the whole license text(s). Honestly, I think it is fine.9 At this stage SPDX is not just long-established in the industry and community, but is also a formal international standard (ISO/IEC 5692:2021). Practically from its beginning – and probably the most known part of the spec – unique names/IDs of licenses and the equivalent canonical texts of those license have been part of SPDX. Which means that if I see SPDX-License-Identifier: MIT I know it specifically means the text that is on https://spdx.org/licenses/MIT.html. Ergo, as long as you are using licenses from the SPDX License List in these tags, all the relevant info is present.

How to keep the tags – Overview of popular minifiers

As mentioned, most minifiers tend to remove comments by default to conserve space. But there is a way to retain license comments (or other important comments). And this method existed for over a decade now!

I have done some research into how different minifiers deal with this. Admittedly, mostly by reading through their documentation. Due to my lack of skills, I did not manage to test out all of them in practice.

But at least theoretically the vast majority of the minifiers that I was told are common (plus a few more I found) seem to support at least one way of keeping important – or even explicitly copyright/license-relevant – comments.

minifierJSDoc-style / @licenseYUI-style / /*!
Google Closure Compiler✔️✔️ 11
Esbuild✔️✔️
JShrink✔️✔️
SWC10✔️✔️
Terser✔️✔️
UglifyJS✔️✔️
YUI Compressor✔️
Bun❌ (🛠️?)

From what I can tell it is only Bun that does not support any way to (selectively) preserve comments. There is a ticket open to implement (at least) the ! method though.

While not themselves minifiers, module bundlers do call and handle minfiers. Here are notes about the ones that I learnt are the most popular ones:

From this overview it seems like using the /*! comment method is our best option – it is short, the most widely supported and not loaded with meaning.

More details on both styles below.

Using @license / JSDoc-style

JSDoc is a markup language used to annotate JavaScript source code files to add in-code documentation, which is then used to generate documentation.

Looking at the JSDoc specification, the following keywords seem relevant:

So from JSDoc it seems the best choice would be @license. To quote from the spec itself:

The @license tag identifies the software license that applies to any portion of your code.

You can use any text to identify the license you are using. If your code uses a standard open-source license, consider using the appropriate identifier from the Software Package Data Exchange (SPDX) License List.

Some JavaScript processing tools, such as Google's Closure Compiler, will automatically preserve any JSDoc comment that includes a @license tag. If you are using one of these tools, you may wish to add a standalone JSDoc comment that includes the @license tag, along with the entire text of the license, so that the license text will be included in generated JavaScript files.

Using /*! / YUI-style

This other style seems to originate from Yahoo! UI Compressor 2.4.8 (also from 2013), to quote its README:

C-style comments starting with /*! are preserved. This is useful with comments containing copyright/license information. As of 2.4.8, the '!' is no longer dropped by YUICompressor. For example:

/*!
* TERMS OF USE - EASING EQUATIONS
* Open source under the BSD License.
* Copyright 2001 Robert Penner All rights reserved.
*/

remains in the output, untouched by YUICompressor.

Many other projects adopted this, some extended it by using also the single-line //!, but others have not.

Also note that YUI itself does not use the double-asterisk /** tag (if it did, it should be /**!), whereas that is typically the starting tag in JSDoc (and JavaDoc) of a document-relevant comment block.

So from the YUI-style tags, it seem using (multi-line) C-style comments that start with /*! is the most consistently used.

And as YUI-style seems to be the most commonly implemented way to tag and preserve (licensing-relevant) comments in JS, it would seem prudent to adopt it for our purposes – to preserve REUSE-standardised tags to mark license and copyright information in files and snippets.

A few PoC I tried

So far the theory …

But when it comes to testing it in practice, it gets both a bit messy and I very quickly reach the limits of my JS skills.

I have tried a few PoC, and ran into mixed, yet promising, results so far.

The most issues, I assume, are fixable by simply changing the settings of the build tools accordingly.

It is entirely possible that a lot of the issues are PEBKAC as well.

Svelte + Rollup + Terser

The simplest PoC I tried is a Svelte app that uses Rollup as a build tool and Terser as the minifier – kindly offered by Oliver “oliwerix” Wagner as a guinea pig. I left the settings as they are, and the results are mostly fine.

First we pull the 29c9881 commit and build it with npm install; npm run build.

In public/build/ we have three files: bundle.css, bundle.js, bundle.js.map.

The bundle.css file does not have any SPDX-* tags, and I suspect this is because it consist solely of 3rd party components, which do not use these tags yet. The public/global.css is still referred to separately in public/index.html and retains the SPDX-* tags in its non-minified form. So that is fine, but would need further testing to check the minified CSS.

The bundle.js file contains the minified JS and the SPDX-* tags remain there, but with one SPDX-SnippetEnd being misplaced.

If we compare e.g. rg SPDX- src/*.js12:

src/store.js
2: * SPDX-SnippetBegin
3: * SPDX-SnippetCopyrightText: 1984 Winston Smith <win@smith.example>
4: * SPDX-License-Identifier: Unlicense
18:/*! SPDX-SnippetEnd */

src/main.js
2: * SPDX-SnippetBegin
3: * SPDX-SnippetCopyrightText: © 2021 Test Dummy <dummy@test.example>
4: * SPDX-License-Identifier: BSD-2-Clause
17:/*! SPDX-SnippetEnd */

… and rg SPDX- public/build/bundle.js

3:     * SPDX-SnippetBegin
4:     * SPDX-SnippetCopyrightText: 1984 Winston Smith <win@smith.example>
5:     * SPDX-License-Identifier: Unlicense
8:/*! SPDX-SnippetEnd */
10:/*! SPDX-SnippetEnd */
13:     * SPDX-SnippetBegin
14:     * SPDX-SnippetCopyrightText: © 2021 Test Dummy <dummy@test.example>
15:     * SPDX-License-Identifier: BSD-2-Clause

… it is clear that something is amiss. A snippet cannot end before it begins.

But when checking the public/build/bundle.js.map SourceMap, we again see the SPDX-* tags in order just fine.

I would really like to know what went wrong here.

React Scripts (+ WebPack + Terser)

Before that I tried to set up a “simple” React Scripts app with the help of my work colleague, Carlos “roclas” Hernandez.

Here, again, I am getting mixed results out of the box.

First we pull the af54954 commit and build it with npm install; npm run build.

On the CSS side, we see that there are two files in build/static/css/, namely: main.05c219f8.css and main.05c219f8.css.map.

Both the main.05c219f8.css and its SourceMap retain the SPDX-* tags where we want them, so that is great!

On the JS side it gets more complicated though. In build/static/js/ we have several files now, and if we pair them up:

  • 453.2a77899f.chunk.js and 453.2a77899f.chunk.js.map
  • main.608edf8e.js, main.608edf8e.js.LICENSE.txt and main.608edf8e.js.map

The 453.2a77899f.chunk.js* files contain no SPDX-* tags. Honestly, I do not know where they came from, but assume it is again 3rd party components, which do not use these tags yet. So we can ignore them.

But it is the main.608edf8e.js* files that we are interested in.

Unfortunately, it is here that it gets a bit annoying.

It seems React Scripts is quite opinionated and hard-codes its preferences when it comes to minification etc. So even though it is easy to set-up WebPack and Terser to preserve (important) comments in the code itself, React forces it otherwise.

What this results in then is the following:

  • main.608edf8e.js is cleaned of all comments – no SPDX-* tags here;
  • but now main.608edf8e.js.LICENSE.txt has all the SPDX-* tags as well other important comments (e.g. @license blocks from 3rd party components);
  • and as for the main.608edf8e.js.map SourceMap, it includes SPDX-* tags, as expected.

The really annoying bit is that it seems like main.608edf8e.js.LICENSE.txt is not mapped, it is just a dump of all the license-related comments. So that does not help us here.

There is a workaround by injecting code and settings using Rewire, but so far I have not managed to set it up correctly. I am sure it is possible, but I gave up after it took way too much of my time already.

Some early thoughts on *.js.map and *.js.LICENSE.txt

If the license and copyright info is missing from the minified source code, but it is there in the *.js.map SourceMap (spec), I think that is better than nothing, but I am leaning towards it not being enough for the goal we are trying to reach here.

Similarly, when the minifier simply shoves all the license comments into a separate *.js.LICENSE.txt file, removing them from the *.js file and without any way to map the license and copyright comments back to the source code, I do not see how this is much more useful than the *.js.map itself.

So far, it seems to me like this is a problem caused by some frameworks (e.g. React Scripts) hard-coding their preferences when it comes to minification, without an easy way to override it.

But if there was a *.js.LICENSE.txt (or equivalent) that was mapped13 via SourceMaps, so one could figure out which license comment matches which source code block in the minified code, I would be inclined to take that as potentially good enough.

Future ideas

Once the base issue of preserving SPDX tags in minified (web front-end) code in proper places is solved, we can expand it to make it even more useful.

Here is a short list of ideas that popped up already. I am keeping them hidden by default, to not detract too much from the base problem.

Nothing stops us from adding more relevant information in these tags – in fact, as long as it is an SPDX tag, that would be in line with both the SPDX standard and the REUSE spec. A prefect candidate to include would be something to designate the origin or provenance of the package the file came from – e.g. using PackageURL.

To make this even more useful in practice, it is entirely imaginable that build tools could help generate or populate these tags and therefore inject information themselves, some early ideas:

  • for (external) packages that do not use REUSE/SPDX Snippet Tags, the build tool could be ordered to generate them from REUSE/SPDX File Tags;
  • same, but to pull the information from a different source (e.g. LICENSE file) – that might be a bit dubious when it comes to exactness though;
  • the above-mentioned PackageURL (or other origin identifier) could be added by the build tool.

All of the above future ideas are super early ideas and some could well be too error-prone to be useful, but should be kept in mind and discussed after the base issue is solved.

Open questions & Call for help and testers

As stated, I am not very savvy when it comes to writing web front-ends, so at this point this project would really benefit from people with experience in building web front-ends taking a look.

If anyone knows how to get React and any other similarly opinionated frameworks to not create the *.js.LICENSE.txt file, but keep the important comments in code, that would be really useful.

If you are a legal or license compliance expert, while I am quite confident in the logic behind this, feedback is very welcome and do try to poke holes in my logic.

If you are a technical person (esp. front-end developers), please, try applying this to code in different build environments and let me know what works and what breaks. We need more and better PoCs.

If you have proposed fixes, even better!

Comments, ideas, suggestions, … welcome.

Ultimately, my goal is to come up with a solution that works and requires (ideally) no changes in tooling and specifications.

If that means abandoning this proposal and finding a better one, so be it. But it has to start somewhere that is half-way doable.

Credits

While I did spend quite a bit of time on this, this would not exist without prior work and a lot of help from others.

First of all, the basic idea of treating each file as a snippet that just happens to currently span a whole file, was originally proposed to me by José Maria “chema” Balsas Falaguera in 2020 and we worked together on an early PoC on how to apply REUSE to a large-ish JS + CSS code-base … before (REUSE Snippets and later) SPDX Snippets came to be.

In fact, it was this Chema’s idea that sparked my quest to first bring snippet support to REUSE, and later to SPDX specifications.

At REUSE I would specifically like to thank Carmen Bianca Bakker, Max Mehl, and Nico Rikken for not refusing this idea upfront and also for being great intellectual sparring partners on this adventure.

And at SPDX it was Alexios “zvr” Zavras who saw the potential and helped me draft SPDX tags for snippets to the point where it got accepted.

I would also like to thank Henrik “hesa” Sandklef, Maximilian Huber and Philippe Ombredanne for their feedback and some proposals on how to expand this further later on.

Of course, none of this would be possible without everyone behind SPDX, REUSE, YUI, and JSDoc.

I am sure I forgot to mention a few other people, and if that was you, I humbly apologise and ask you to let me know, so I can correct this issue.

hook out → wow, this took longer than expected … so many rabbit holes!


  1. Also called browser-side code. 

  2. In droit d’auteur / civil law jurisdictions, the removal of a copyright statement could also qualify as a violation of civil law or even a criminal offence … when the copyright holder is an author (physical person), and not a legal entity

  3. Both from the point-of-view of a license compliance expert and their tooling; and from (at least) the spirit of FOSS licenses. 

  4. Barring any magical weirdness that happens during the minifying/uglifying that might mess things up in things I did not predict. Here I need front-end experts’ help and more testing. 

  5. If unreadable binary is a blob, “barf” sounded like an appropriate equivalent for nigh-unreadable minified code. 

  6. Here is where the added functionality of treating every file as a (very-long) snippet pays off. If we did not mark the start and end of each file, we would not be able to tell that in the minified code. In that case, a consequence would be that we would falsely assume a piece of minified code would fall under a certain license, whereas it would simply be part of a file that was unmarked (e.g. pulled in from a 3rd party component). 

  7. Probably Apache-2.0’s requirement to include the NOTICE file would not be satisfied, but that is not a new issue. And there are three ways to make Apache-2.0 happy, so it is solvable if you are diligent. 

  8. The value of an SPDX-License-Identifier tag is an SPDX License Expression, which can either be just a single license or a logical statement of several licenses. (Hat-tip to Peter Monks for pointing this out.) 

  9. I do accept that there is a tiny bit of handwaving involved here, but honestly, I think this should be good enough, given the limitations of minified code. If the consensus ends up that full license texts need to be shipped as well, this can be done via hyperlinks, and those can be generated from the SPDX IDs. Or if you really want to be pedantic about it, upload and hyperlink the (usually missing) license texts from each JS/CSS package itself. 

  10. SWC is primarily a compiler and module bundler, but I keep it here because it does its own minification instead of relying on an external tool. 

  11. It is not documented but Google Closure Compiler does indeed support ! and treats it explicitly as a license block (without any additional JSDoc parsing) since 2016

  12. I use RipGrep in this example, but normal grep should work too. 

  13. And I do not know of any that does that. 

From Fri, Sep 6th to Tue, Sep 10th I attended the 2024 edition of KDE Akademy in Würzburg, Germany. I booked a room in a hotel downtown the same place CoLa, a fellow KDE developer, stayed. Since parking is rather expensive in downtown areas, I left the car in front of the university building where the event was about to start on Saturday morning and took the bus into the city to the hotel. We all used the bus in coming days and one would always meet some KDE folks easy to spot wearing their lanyards.

On Friday night the KDE crowd gathered at a pub in the city and it was great to see old friends and also meet new people. At some point, I was talking to Carlos. It turned out that he already made some contributions to KMyMoney. The git log says it was in 2022. While more and more fellow KDE developers joined the place it became louder and louder and conversations were not easy anymore. Too bad that some of us got stranded at different places on their way out to Würzburg and did not make it until Saturday.

Conference

On Saturday, the conference program started with a keynote by Joanna Murzyn who took us on a journey from crucial mineral mining hubs to electronic waste dumpsters, uncovering the intricate connections between code, hardware, open source principles as well as social and environmental justice. We discovered how the KDE community’s work is shaping a more resilient, regenerative future, and explore ways to extend those principles to create positive impact beyond tech world.

On the first day, I took the opportunity to see the following talks

  • Current Developments in KDE Hardware Integration
  • KDE to Make Wines — Using KDE Software on Enterprise Desktops a Return on Experience
  • KWin Effects: The Next Generation
  • Adapt or Die: How new Linux packaging approaches affect wider KDE
  • An Operating System of Our Own
  • What’s a Maintainer anyway?

The last one for the day complemented the keynote in a nice way. In KDE newcomer Nicole Teale’s talk entitled “Getting Them Early: Teaching Pupils About The Environmental Benefits Of FOSS” she presented the work she is doing introducing KDE/FOSS to pupils, with a focus on its environmental benefits. She shared ideas on how to get schools involved in teaching pupils about reusing old hardware with FOSS. and presented some of the projects that have already been implemented in schools in Germany. This project is funded by the Umweltbundesamt (UBA) called “Sustainable Software For Sustainable Hardware”. The goal of this project is to reduce e-waste by promoting the adoption of KDE / Free & Open Source Software (FOSS) and raising awareness about the critical role software plays in the long-term, efficient use of hardware.

This becomes important in 2025 when Windows 10 runs out of support and Windows 11 requires new hardware, even though the existing one is still perfectly suited for the requirements of the majority of people. Linux and KDE to the rescue.

Saturday ended with Pizza and beer at the university as the booked beer garden canceled the reservation due to approaching thunderstorms.

On Sunday, I saw the following talks

  • Openwashing – How do we handle (and enforce?) OSS policies in products?
  • Opt In? Opt Out? Opt Green! KDE Eco’s New Sustainability Initiative
  • KDE’s CI and CD infrastructure
  • The Road to KDE Neon Core — Gosh! We’re surrounded by Snaps everywhere!

and of course the KDE Akademy award ceremony. In between those talks I had a chance to meet Julius Künzel and take a look at the problems we have in the KMyMoney project with the MacOS CD builds. He spotted a few things but I did not have the time to take care of them yet.

As a tradition, on Sunday is also the gathering to take the group picture. Here’s this years edition:

CC-BY-SA 4.0 by Andy Betts

Birds of a feather sessions

On Monday and Tuesday I went to various BoF’s and took the opportunity to join the git/Gitlab presentation by Natalie Clarius. I learned a few subtleties of Gitlab that I didn’t know before, so it was worth it. In the meantime I talked with a lot of people and did a small bit of hacking (one bug fixed). The BoFs I joined:

Good-bye Akademy 2024 / Thank you volunteers

Tuesday afternoon was the time to wave good-bye to the fellow KDE people and drive back home which I reached without delay (no traffic on the road) after an hour and a half. Hopefully, I will be able to join next time. Next stop will be the auditing of KDE accounting coming up in Berlin in a few weeks.

A big thank you goes out to the numerous volunteers who made this event happen. The team around seaLne just did a marvelous job.

Monday, 16 September 2024

Back from the fun of Akademy in Würzburg we can now get to the important task of testing Plasma 6.2 beta. It’s now in KDE neon testing edition which we build from the Git branches which will be used to make the 6.2 final release in 2.5 weeks time. Grab it now or if you don’t have a machine to install it on you can try the Docker images using the simple command `neondocker -p -e testing`.

Saturday, 14 September 2024

This year’s Akademy was a special one for me in many ways.

First of all, instead of me travelling to Akademy it took place in my hometown of Würzburg, Germany. While I did have a hand in organizing it, most of the credit for it goes to Tobias and David. I had a lot of fun introducing people to my area and the concept of drinking wine on a bridge.

Qt Contributor Summit

Right before Akademy there was the Qt Contributor Summit, also in Würzburg (what a coincidence!). It was great to meet old and new Qt faces and talk about topics that are relevant to KDE, like the upcoming migration of KDE API documentation to qdoc.

Akademy Talks

This Akademy I gave two talks: One long one where I looked back at the Qt6/KF6 transition, what went well, what didn’t, and looked towards the future of what’s next for our software platform. Then I also had a lightning talk where I talked about the role of maintainers in open-source projects, why KDE doesn’t have traditional maintainers, and why that’s a good thing.

Besides that there were also a lot of interesting talks from other people, too many to mention right now. Speaking as a member of the program committee we had some tough decisions to make about what to include.

Goals

During the conference we announced the new set of Goals that were recently elected. I’m excited that my own proposal “Streamlined Application Development Experience” got selected and I’m looking forward to working on it with you. Besides that I also want to see how I can help out with the other elected goals: “We care about your input” and “KDE needs you 🫵”.

Akademy Awards

Another way this Akademy was special for me is that I was awarded with an Akademy award for my work on KDE Frameworks and Plasma. It feels great to get recognition for all the work I’ve been doing for the last seven years.

A framed award titling: Non-Application Contribution Award September 2024 Nicolas Fella for their work on KDE Frameworks and Plasma

BOFs

During the week we had lots of smaller meetings and workshops (a.k.a BOFs, world’s most terrible acronym). I was leading two of them, one about my newly-elected goal where I was presenting my proposal in more detail, and one about the ongoing work of mine to migrate our API documentation to qdoc. Thanks to our sysadmin Ben we now have a website where the current (still very much WIP) state of the new API documentation page can be seen.

Other Things

What’s great about Akademy isn’t just talks and BOFs, it’s meeting people you only see online all year, talking to them in person, getting your code reviewed while staring on a screen together, chatting over random visions, complaining about things, laughing and enjoying things together, and wrapping up the day with a nice beer in your hand.

I’m already looking forward to next year’s Akademy, wherever that will be. Maybe it will be your place, organizing it is a lot less scary than you’d think ;).

Saturday, 27 July 2024

Future 聊天室

功能

账号管理

  • 实现登录、注册、注销
  • 实现找回密码(提高)

好友管理

  • 实现好友的添加、删除、查询操作
  • 实现显示好友在线状态
  • 禁止不存在好友关系的用户间的私聊
  • 实现屏蔽好友消息
  • 实现好友间聊天

群管理

  • 实现群组的创建、解散
  • 实现用户申请加入群组
  • 实现用户查看已加入的群组
  • 实现群组成员退出已加入的群组
  • 实现群组成员查看群组成员列表
  • 实现群主对群组管理员的添加和删除
  • 实现群组管理员批准用户加入群组
  • 实现群组管理员/群主从群组中移除用户
  • 实现群组内聊天功能

聊天功能

  • 实现查看历史消息记录
  • 实现用户间在线聊天
  • 实现在线用户对离线用户发送消息,离线用户上线后获得通知
  • 实现在线发送文件
  • 实现在线用户对离线用户发送文件,离线用户上线后获得通知/接收
  • 实现后台发送文件
  • 实现用户在线时,消息的实时通知
    • 收到好友请求
    • 收到私聊
    • 收到加群申请

其他

  • 使用 C++编程语言
  • 使用 I/O 多路复用完成本项目
    • C++:Epoll ET 模式
  • 使用数据库完成数据存储
    • Redis 和 mysql
    • 历史消息采用redis做告诉缓存,mysql来存储大量历史消息
  • 数据库中数据的存储和取用使用序列化和反序列化完成(Json)
  • 支持大量客户端同时访问
  • 实现服务器日志,记录服务器的状态信息
  • C/S 双端均支持在 CLI/Web 自行指定 IP:Port
  • 实现具有高稳定性的客户端和服务器,防止在用户非法输入时崩溃或异常
    • 实现 TCP 心跳检测

遇到的问题

  • 在客户端因某些原因异常退出时,服务端将无法正常处理其他请求.

    • 开始时服务端没有将新连接的描述符设置为非阻塞模式,导致客户端异常退出时服务端的接收recv一直在阻塞着读取,且无法处理其他请求
  • 如何往线程池中传入成员函数.

    • auto task = [](){     类的成员函数 } m_pool.submit(task);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30

      * `Tcp`传输为字节流传输,采用在每条消息前面加上`长度`来正确接收每条请求.
      * 在代码中往`redis`数据库中添加好友申请数据时,没有正确插入.
      * 由于好友申请当中包含时间,姓名等信息,中间含有**空格**,在redis的哈希表中无法插入.
      * 经过查阅在redis哈希表中,**空格**可以用`+`代替,在遇到`+`时会被转义为**空格**,或者存储为**十六进制**,但是这种方法在终端中的**空格**为灰色.

      * 在聊天界面如果输入带有`%`,服务器直接挂掉.

      * ```c++
      int redisAsyncContext::Lpush(const std::string &key, const std::string &value)
      {
      //std::string cmd = "lpush " + key + " " + value;
      this->m_reply = (redisReply *)redisCommand(this->m_connettion, "LPUSH %s %s", key.c_str(), value.c_str());
      // 检查 m_reply 是否为 NULL
      if (this->m_reply == NULL) {
      std::cerr << "Error: redisCommand returned NULL" << std::endl;
      return -1; // 或其他合适的错误值
      }

      // 检查 m_reply 类型
      if (this->m_reply->type != REDIS_REPLY_INTEGER) {
      std::cerr << "Error: Expected integer reply" << std::endl;
      freeReplyObject(this->m_reply);
      return -1; // 或其他合适的错误值
      }
      int num = this->m_reply->integer;
      freeReplyObject(this->m_reply);
      std::cout << "G" << std::endl;
      return num;
      }
    • 在执行下面两句是,%会被替换为cmd.c_str(),导致无法将消息插入历史记录表,导致出现错误.

      1
      2
      std::string cmd = "lpush " + key + " " + value;
      this->m_reply = (redisReply *)redisCommand(this->m_connettion, cmd.c_str());
    • 只要更改为下面即没有问题

      1
      this->m_reply = (redisReply *)redisCommand(this->m_connettion, "LPUSH %s %s", key.c_str(), value.c_str());
  • 在某次测试时,新注册账号id为0614897828,导致与该账号聊天的账号进入聊天界面时,0614897828发送的消息会在他人界面显示为通知消息.

    • 由于与人聊天时会在数据库中创建有排序集合来记录某人正在聊天的人的id.运行时通过查看发现,其余账号与0614897828聊天时,数据库中记录账号为614897828.
    • 更换为首位非0的id就不会出现该问题.
    • redis会将有序集合中的score字段字符串中的0去除.将从字符串的首位非0开始存储.
  • 在进入好友私聊界面时,客户端新建了来接收消息,同时主线程发消息,主线程通过判断用户是否键入Esc来退出聊天界面,在输入Esc时,接收消息线程无法正确回收,无法退出聊天界面.

    • 在接收消息线程中来判断标志位来结束该线程,主线程在键入Esc时,将标志位设置为false,但由于客户端的recv为阻塞,导致无法及时获取到标志位的更改.
    • 在键入Esc时,服务器向客户端发送退出信号,这样就可以正确及时的获取到标志位的更改,从而正确退出.
  • 在进行发送文件时,服务器会先将文件存储在本地,但是服务器存储的文件会多写入一些信息,导致接收文件大小偏大.

    • 由于在用户登陆进入之后,会有一个线程每5秒向服务器送送刷新请求,导致服务器会将刷新请求当作文件内容写入文件,导致接收文件不一致
    • 在进入文件收发菜单后,关闭实时刷新,在文件结束后,再将实时刷新打开.
  • 实现后台发送文件,由于会有实时刷新的存在,会导致文件发送不准确.

    • 在发送文件时,重新创建一个socket连接到服务器,在将发送这个文件交给另外一个线程
    • 初始时在用户选择发送文件时开始线程,但会导致主线程与发送线程会同时运行菜单.主线程继续循环菜单,而发送线程运行获取发送文件的信息。
      • 更改线程开始时间,在用户选择发送文件时,获取到发送文件信息后,在启动线程.
      • 新连接的socket发送文件后,服务器不用手动断开连接,服务器有心跳检测,会在一段时间后断开.

eventfd

详解

是一个Linux系统调用,也是一种进程间通信(IPC)机制,主要通过使用文件描述符生成和使用事件通知.

提供了一种在不同进程之间或同一进程内的线程之间的同步事件的方法.

异常处理

异常是程序在执行期间产生的问题。c++异常是指在程序运行时发生的特殊情况.

异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw

关键字:try、catch、throw

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常

  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块

1
2
3
4
5
6
7
8
9
10
11
12
13
try
{
// 保护代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}

nlohmann::json

链接

SIGPIPE

SIGPIPE 是一种信号,当一个进程尝试向一个已经关闭的或不可写的管道或套接字写数据时,会触发这个信号。默认情况下,接收到 SIGPIPE 信号的进程会终止。这在网络编程中会导致一些问题,因为如果客户端断开连接,服务器进程在写入数据时会因为这个信号而意外退出。

在网络编程中,特别是使用套接字进行通信时,忽略 SIGPIPE 信号是一个常见的做法。这样可以防止进程因为 SIGPIPE 信号而意外终止。相反,程序可以通过检测 sendwrite 操作的返回值来处理错误,从而使程序更加健壮。

后续问题

  1. 初始客户端构思方面的缺陷,导致实时消息没有线程及时接收。

    当前处理方式为服务端将通知消息放入数据库当中,客户端在登陆之后,增加一个线程来定时执行刷新函数,刷新数据库中的通知消息

    可以优化为在客户端登陆之后,采用多线程的方式来处理相关操作.

  2. 目前只有在历史消息这一操作涉及到redis+mysql的处理方式.

    可以将一些重要信息也采用redis+mysql的处理方式(涉及到redis的存储方式,redis存储于内存当中,会造成数据丢失的情况)

    可以考虑将redis全部做为缓存的形式,将重要信息与历史消息类似,达到一定情况下放入mysql,使数据更加的持久化.

后续学习

  1. 继续学习epoll的相关内容.

  2. 了解其余网络框架,使得该项目的服务端更加的健壮.

  3. 了解消息队列的信息同步的问题.

  4. 熟悉零拷贝的过程,以及零拷贝的具体实现.

  5. 序列化的相关协议,json与其他序列化的区别、json的优点,以及为什么不用其他的序列化

  6. 数据库的深入学习,了解redis的缓存穿透、缓存雪崩、缓存击穿。加强对mysql数据库的学习.

  7. 加强对网络协议相关知识的学习.

Friday, 17 May 2024

强制类型转换

C++提供了四个强制类型转换的关键字:

  • static_cast
  • const_cast
  • reinterpret_cast
  • ``dynamic_cast`

static_cast

1
static_cast<目标类型>(表达式)
1
2
int num = 2;
double result =static_cast<double>(num);

该运算符将表达式转换为目标类型。但没有进行运行时类型检查来保证转换的安全性

主要用法

  1. 用于类层次结构中父类和子类之间指针或引用的转换.进行上行转换是安全的(即将子类的指针或引用转换成父类是正确的);进行下行转换的时候,由于没有动态类型检查,所以是不安全的。继承必须为public
  2. 用于基本类型之间的转换,如intchar安全性也需要程序员来保证
  3. 把空指针转换为目标类型的空指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person
{
public:
void print()
{
cout << "Person " << endl;
}
};

class Son : public Person
{
public:
void print()
{
cout << "Son " << endl;
}
};

void print1(Person *p)
{
p->print();
}

int main()
{
Son s;
print1(static_cast<Person *>(&s));
return 0;
}

const_cast

const_cast是c++中专用于处理与const相关的强制类型转换的关键字

其功能为:为一个变量重新设定其const描述.

即:const_cast可以为一个变量强行增加或删除其const限定.

需要明确的是,即使用户通过const_cast强行去除了const属性,也不代表当前变量从不可变变为了可变。const_cast只是使得用户接管了编译器对于const限定的管理权,故用户必须遵守“不修改变量”的承诺。如果违反此承诺,编译器也不会因此而引发编译时错误,但可能引发运行时错误。

  1. const_cast可用于更改const成员函数内的非const类成员。
  2. const_cast可用于将const数据传递给不接收const的函数。
  3. const_cast<>里边的内容必须是引用或者指针。
  4. const_cast也可以用来抛弃volatile__unaligned属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Student
{
private:
int roll;
public:
Student(int r) :roll(r) {}
void fun() const
{
(const_cast<Student*> (this))->roll = 5;
}
int getRoll() { return roll; }
};

int main() {
Student student(3);
std::cout << "Old roll number: " << student.getRoll() << std::endl;
student.fun();
std::cout << "New roll number: " << student.getRoll() << std::endl;

// const_cast只能调节类型限定符,不能更改基础类型
int a1 = 40;
//const int* b1 = &a1;
//char* c1 = const_cast <char*> (b1); // 编译程序时出错

const volatile int* d1 = &a1;
std::cout << "typeid of d1 " << typeid(d1).name() << '\n'; // int const volatile *
int* e1 = const_cast <int*> (d1);
std::cout << "typeid of e1 " << typeid(e1).name() << '\n'; // int *

return 0;
}

在const成员函数fun()中,编译器将“this”视为“ const student const this”,即“this”是指向常量对象的常量指针,因此编译器不允许通过以下方式更改数据成员“这个”指针。const_cast将“this”指针的类型更改为“student const this”

const_cast比简单类型转换更安全。从某种意义上讲,如果强制类型与原始对象不相同,则强制转换不会发生,这是比较安全的。

1
2
3
int a=20;
const int *p=&a;
char *c1=const_cast<char*> (p);//编译时程序出错

reinterpret_cast

reinterpret,即重新解释.

该强制类型转换的作用是提供某个变量在底层数据上的重新解释.

当我们对一个变量使用reinterpret_cast后,编译器将无视任何不合理行为,强行将被转换变量的内存数据重解释为某个新的类型。用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。 它不检查指针类型和指针所指向的数据是否相同。

需要注意的是,reinterpret_cast要求转换的两个数据所占用的内存大小一致,否则会引发编译时错误.

1
data_type *var_name = reinterpret_cast <data_type *>(pointer_variable);

使用 reinterpret_cast 的目的:

  1. reinterpret_cast是一种非常特殊且危险的类型转换操作符。并且建议使用适当的数据类型使用它,即(指针数据类型应与原始数据类型相同)。
  2. 它可以将任何指针类型转换为任何其他数据类型。
  3. 当我们要使用位时使用它。
  4. 它仅用于将任何指针转换为原始类型。
  5. 布尔值将转换为整数值,即0表示false,1表示true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A {
public:
int a;
A(int i) :a(i) {}
void fun_a()
{
std::cout << "In class A\n";
}
};

class B {
public:
int b;
B(int i) :b(i) {}
void fun_b()
{
std::cout << "In class B\n";
}
};

void testReinterpretCast() {
B *x = new B(5);
A* y = reinterpret_cast<A*>(x);
y->fun_a(); // In class A
std::cout << y->a << std::endl; // 5
}

dynamic_cast

dynamic用于在运行时实现向下类型转换。

1
dynamic_cast <type-id> (expression)

expression转换为type-id类型,type-id必须是类的指针,类的引用或者是void,

如果type-id是一个指针,那么expression也是一个指针,是引用的话同为引用

特点如下:

  1. 它是在运行是进行处理的,其余三个都是在编译时完成. 运行时进行类型检查
  2. 不能用于内置的基本数据类型之间的强制转换
  3. dynamic_cast 要求 <> 内所描述的目标类型必须为指针或引用。dynamic_cast 转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回 nullptr
  4. 在类的转换时,在类层次上进行向上转换(子类指针指向父类指针),与static_cast的效果是一样的。在进行父类指针向子类指针的转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
  5. 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。
  6. 使用 dynamic_cast 进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class AA {
public:
virtual void print() {
cout << "in class AA" << endl;
};
};

class BB :public AA {
public:
void print() {
cout << "in class BB" << endl;
};
};

void testDynamicCast() {
AA* a1 = new BB; // a1是A类型的指针指向一个B类型的对象
AA* a2 = new AA; // a2是A类型的指针指向一个A类型的对象

BB* b1, * b2, * b3, * b4;

b1 = dynamic_cast<BB*>(a1);// not null,向下转换成功,a1 之前指向的就是 B 类型的对象,所以可以转换成 B 类型的指针。
if (b1 == nullptr)
cout << "b1 is null" << endl;
else
cout << "b1 is not null" << endl;

b2 = dynamic_cast<BB*>(a2);// null,向下转换失败
if (b2 == nullptr)
cout << "b2 is null" << endl;
else
cout << "b2 is not null" << endl;

// 用 static_cast,Resharper C++ 会提示修改为 dynamic_cast
b3 = static_cast<BB*>(a1);// not null
if (b3 == nullptr)
cout << "b3 is null" << endl;
else
cout << "b3 is not null" << endl;

b4 = static_cast<BB*>(a2);// not null
if (b4 == nullptr)
cout << "b4 is null" << endl;
else
cout << "b4 is not null" << endl;

a1->print();// in class BB
a2->print();// in class AA

b1->print();// in class BB
//b2->print(); // null 引发异常
b3->print();// in class BB
b4->print();// in class AA
}

详细

Friday, 10 May 2024

Lambda表达式

匿名函数是很多高级语言都支持的概念,如lisp语言在1958年首先采用匿名函数。匿名函数有函数体,但没有函数名。C++11中引入了lambda表达式。利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读。但是从本质上来讲,lambda表达式只是一种语法糖,因为所有其能完成的工作都可以用其它稍微复杂的代码来实现。但是它简便的语法却给C++带来了深远的影响。如果从广义上说,lamdba表达式产生的是函数对象。

相同类似功能我们也可以使用函数对象或者函数指针实现:函数对象能维护状态,但语法开销大,而函数指针语法开销小,却没法保存范围内的状态。lambda表达式正是结合了两者的优点。

声明Lambda表达式

1
2
3
4
5
6
7
// 完整语法
[capture list] (params list) mutable(optional) constexpr(optional)(c++17) exception attribute -> return type { function body };

// 可选的简化语法
[capture list] (params list) -> return type {function body}; //1
[capture list] (params list) {function body};//2
[capture list] {function body};//3
  • capture list:捕获外部变量列表,不能省略;
  • params list:形参列表,可以省略(但是后面必须紧跟函数体);
  • mutable指示符: 可选,将lambda表达式标记为mutable后,函数体就可以修改传值方式捕获的变量;
  • constexpr:可选,C++17,可以指定lambda表达式是一个常量函数;
  • exception:异常设定, 可选,指定lambda表达式可以抛出的异常;
  • attribute:可选,指定lambda表达式的特性;
  • return type:返回类型
  • function body:函数体

标号1. 函数声明了一个const类型的表达式,此声明不可改变capture list中的捕获的值。

标号2. 函数省略了返回值,此时如果function body内含有return语句,则按return语句返回类型决定返回值类型,若无则返回值为void类型。

标号3. 函数无参数列表,意味无参函数。

1
2
3
vector<int> vec{1,0,9,5,3,3,7,8,2};

sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });

捕获外部变量

lambda表达式最前面的方括号的意义何在?其实这是lambda表达式一个很Hong要的功能,就是闭包。这里我们先讲一下lambda表达式的大致原理:每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,其实一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。

Lambda表达式可以捕获外面变量,但需要我们提供一个谓词函数([capture list]在声明表达式最前)。类似参数传递方式:值传递、引入传递、指针传递。在Lambda表达式中,外部变量捕获方式也类似:值捕获、引用捕获、隐式捕获

值捕获

1
2
3
4
5
int a = 123;
auto f = [a] { cout << a << endl; };
f(); // 输出:123
a = 321;
f(); // 输出:123

值捕获和参数传递中的值传递类似,被捕获的值在Lambda表达式创建时通过值拷贝的方式传入,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。因此Lambda表达式函数体中不能修改该外部变量的值; 因为函数调用运算符的重载方法是const属性的。同样,函数体外对于值的修改也不会改变被捕获的值。 想改动传值方式捕获的值,那么就要使用mutable

1
2
3
auto add_x = [x](int a) mutable { x *= 2; return a + x; };  // 复制捕捉x

cout << add_x(10) << endl; // 输出:30

因为一旦将lambda表达式标记为mutable,那么实现的函数调用运算符是非const属性的。

引用捕获

1
2
3
4
int a = 123;
auto f = [&a] { cout << a << endl; };
a = 321;
f(); // 输出:321

引用捕获的变量使用的实际上就是该引用所绑定的对象,因此引用对象的改变会改变函数体内对该对象的引用的值。 对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。

隐式捕获

隐式捕获有两种方式,分别是 [=]:以值补获的方式捕获外部所有变量 [&]:表示以引用捕获的方式捕获外部所有变量

1
2
3
int a = 123, b=321;
auto df = [=] { cout << a << b << endl; }; // 值捕获
auto rf = [&] { cout << a << b << endl; }; // 引用捕获

其他

捕获外部变量形式
[ ]不捕获任何变量(无参函数)
[变量1,&变量2, …]值(引用)形式捕获指定的多个外部变量
[this]值捕获this指针
[=, &x]变量x以引用形式捕获,其余变量以传值形式捕获
[*this]通过传值方式捕获当前对象
[&, x]默认以引用捕获所有变量,但是x是例外,通过值捕获

既然只使用一次,那直接写全代码不就行了,为啥要函数呢?——因为lambda可以捕获局部变量

在上面的捕获方式中,注意最好不要使用[=][&]默认捕获所有变量。首先说默认引用捕获所有变量,你有很大可能会出现悬挂引用(Dangling references),因为引用捕获不会延长引用的变量的声明周期:

1
2
3
4
std::function<int(int)> add_x(int x)
{
return [&](int a) {return x+a;};
}

因为参数x仅是一个临时变量,函数调用后就被销毁,但是返回的lambda表达式却引用了该变量,但调用这个表达式时,引用的是一个垃圾值,所以会产生没有意义的结果。如果通过传值的方式来解决上面的问题:

1
2
3
4
std::function<int(int)> add_x(int x)
{
return [=](int a) { return x + a; };
}

使用默认传值方式可以便面悬挂引用问题.但是采用默认值捕获所有变量仍然有风险。例如当在类中捕获私有变量,当返回值为lambda表达式时,无法捕获到私有变量,但当指定为[=]时,会捕获到this指针的副本,当类已经调用析构函数,使用该指针仍然不安全.

参数

  • 参数列表中不能有默认参数
  • 不支持可变参数
  • 所有参数必须要参数名(相当于不可以有占位参数)

返回类型

单一的return语句可以推断返回类型;多语句则默认返回void,否则报错,应指定返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 正确,单一return语句
transform(vi.begin(),vi.end(),vi.begin(),
[] (int i) {
return i<0? -i; i;
}
);
// 错误。不能推断返回类型
transform(vi.begin(),vi.end(),vi.begin(),
[] (int i) {
if (I<0) return -i;
else return i;
}
);
// 正确,尾置返回类型
transform(vi.begin(),vi.end(),vi.begin(),
[] (int i) ->int{
if (I<0)
return -i;
else
return i;
}
);

赋值

auto和function可接受lambda表达式的返回:

1
2
3
int x =8,y=9;
auto add = [](int a,int b){return a+b;};
std::function<int(int,int)> Add = [=] (int a,int b){return a+b};

lambda表达式产生的类不含有默认构造函数、赋值运算符、默认析构函数。至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,看来与具体实现有关。还有一点要注意:lambda表达式是不能被赋值的:

1
2
3
4
5
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };

a = b; // 非法,lambda无法赋值
auto c = a; // 合法,生成一个副本

因为禁用了赋值操作符:

1
ClosureType& operator=(const ClosureType&) = delete;

但是没有禁用复制构造函数,所以可以用一个lambda表达式去初始化另外一个lambda表达式而产生副本。并且lambda表达式也可以赋值给相对应的函数指针,这也使得你完全可以把lambda表达式看成对应函数类型的指针

新特性

C++14中,lambda又得到了增强,一个是泛型lambda表达式,一个是lambda可以捕捉表达式。

lambda捕捉表达式

lambda表达式可以按复制或者引用捕获在其作用域范围内的变量。而有时候,我们希望捕捉不在其作用域范围内的变量,而且最重要的是我们希望捕捉右值。所以C++14中引入了表达式捕捉,其允许用任何类型的表达式初始化捕捉的变量:

1
2
3
4
5
6
7
8
// 利用表达式捕获,可以更灵活地处理作用域内的变量
int x = 4;
auto y = [&r = x, x = x + 1] { r += 2; return x * x; }();
// 此时 x 更新为6,y 为25

// 直接用字面值初始化变量
auto z = [str = "string"]{ return str; }();
// 此时z是const char* 类型,存储字符串 string

可以看到捕捉表达式扩大了lambda表达式的捕捉能力,有时候你可以用std::move初始化变量。这对不能复制只能移动的对象很重要,比如std::unique_ptr,因为其不支持复制操作,你无法以值方式捕捉到它。但是利用lambda捕捉表达式,可以通过移动来捕捉它:

1
2
3
4
auto myPi = std::make_unique<double>(3.1415);

auto circle_area = [pi = std::move(myPi)](double r) { return *pi * r * r; };
cout << circle_area(1.0) << endl; // 3.1415

原文章

Friday, 3 May 2024

可变参数模板

可变参数模板是C++11引入的一个特性,它允许定义一个接受任意数量参数的模板.

在函数模板或类模板中,我们可以使用可变参数模板来定义接受任意数量参数的函数或类。可变参数模板使用了模板参数包(template parameter pack),它允许我们将零个或多个模板参数打包成一个参数集合。

1
2
3
4
5
6
7
8
9
template <typename T>
T sum(T arg){
return arg;
}

template<typename T,typename...Args>
T sum(T first ,Args...res){
return first+sum(rest...);
}

尾部类型推导

当需要使用尾部类型推导的时候,通常是因为函数的返回类型依赖于函数参数或其他的上下文.

1
2
3
4
5
6
7
8
9
10
11
12
auto add=[](int x,int y)->int {
return x+y;
}
auto add=[](int x,int y)->auto
{
return x+y;
}
//函数模板
template<typename T, typename U>
auto multiply(T x, U y) -> decltype(x * y) {
return x * y;
}

std::accumulate

std::accumulate 是 C++ 标准库 <numeric> 头文件中提供的一个算法函数,用于计算给定范围内的元素的总和。它接受一个起始迭代器和一个终止迭代器,以及一个初始值,然后对范围内的元素进行累加,并返回结果。

它的基本语法如下:

1
2
cppCopy codetemplate< class InputIt, class T >
T accumulate( InputIt first, InputIt last, T init );

其中:

  • InputIt 是起始迭代器和终止迭代器的类型。
  • first 是范围的起始迭代器。
  • last 是范围的终止迭代器(不包括在范围内)。
  • init 是初始值,用于累加元素。

decltype

decltype 是 C++11 引入的一个关键字,用于获取表达式的类型。它可以用于声明变量、函数返回类型、模板参数等地方。

1
decltype(expression)

std::bind

链接

std::bind的头文件是

解释

std::result_of

std::result_of 是一个 C++11 标准库中的模板工具,用于获取函数对象的返回类型。它可以用于在编译时确定函数对象的返回类型,而不需要实际调用该函数对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <type_traits>

int add(int a, int b) {
return a + b;
}

int main() {
// 获取 add 函数的返回类型
typedef std::result_of<decltype(add)&(int, int)>::type result_type;

// 输出返回类型
std::cout << "Result type: " << typeid(result_type).name() << std::endl;

return 0;
}

std::result_ofdecltype 都是用于获取表达式的类型,但它们之间有一些区别:

  1. 用法
    • std::result_of 用于获取函数对象的返回类型,而不需要实际调用该函数对象。它是一个模板类,需要指定函数对象的类型和参数类型。
    • decltype 用于获取表达式的类型,可以是任意的表达式,不仅限于函数调用。它是一个关键字,可以直接用于变量声明、返回类型推断等地方。
  2. 适用范围
    • std::result_of 主要用于获取函数对象的返回类型,因此在涉及函数对象的场景下更为常见。
    • decltype 不仅可以用于函数调用,还可以用于变量声明、表达式的类型推断等场景,更为灵活。
  3. 语法
    • std::result_of 的语法是模板类模板参数,需要传递函数类型和参数类型。
    • decltype 的语法更为灵活,可以直接对任意表达式使用。

std::filesvstem

cppreference

csdn