Don't use static (outside of classes) in modern C++

  Рет қаралды 4,975

Code for yourself

Code for yourself

Күн бұрын

Everything that you wanted to know about static (when it is used outside of classes) but were afraid to ask. The video on using static in classes can be found here: • Use static in classes ...
UPD: Had to re-record and re-upload due to some issues with the previous attempt at this video.
📚 As always, the script to the video lives here: github.com/cpp-for-yourself/s...
00:00 - Start
00:27 - Don't use static outside of classes!
01:00 - What static actually controls
01:25 - Storage duration
04:32 - A glimpse into singleton pattern
05:23 - Summary of using static for storage duration control
05:56 - Linkage
08:22 - How to figure out linkage
09:24 - Don't use static to control linkage
14:01 - Summary
14:19 - Guideline on inline and static use
15:30 - Outro

Пікірлер: 65
@CodeForYourself
@CodeForYourself 8 ай бұрын
Please don't be confused. This is a re-record and a re-upload of the previous video that had a couple of issues with it. I hope that this attempt tailors its message better. You will find the second half of the video being completely new. Thanks for your feedback and keep it coming!
@aurelienregat-barrel9217
@aurelienregat-barrel9217 7 ай бұрын
There's a benefit in using static for a free function in a cpp file rather than an anonymous namespace: if the function is not called, then you get a warning from the compiler (at least with VC++). Which helps detect and remove dead code.
@CodeForYourself
@CodeForYourself 7 ай бұрын
That is a good point. Probably there is a linter that can help here too.
@AnasKhedr
@AnasKhedr 7 ай бұрын
This video is absolute gold. It drove me into a deep search for storage durations. I never knew that constexpr vars in the header had internal linkage for each transaction unit 🙀. Thanks for the inline tip and correcting this misconception.
@CodeForYourself
@CodeForYourself 7 ай бұрын
To be fair, I thought I know everything about this topic when I started preparing the video and it still took a long time to figure out all the intricate details 😅 I guess what I’m trying to say here is that I think I know how you feel 😅
@SFV4
@SFV4 7 ай бұрын
9:00: your chart is missing ‘extern’ And for those working on Windows with dlls, then there are dllexport & dllimport
@CodeForYourself
@CodeForYourself 7 ай бұрын
Well, I deliberately don’t talk about every feature that exists in C++. I’ve never had a situation in my life when I had to use extern, apart from maybe language bindings. That is why it is not here at all. As for windows, thanks for the suggestion, I’m not a windows expert. But then again, I’m covering mostly things from the standard here.
@bsdooby
@bsdooby 8 ай бұрын
Nice that you take feedback seriously :)
@CodeForYourself
@CodeForYourself 8 ай бұрын
I sure try to. I think it is very important when teaching anything. So please keep it coming!
@SystemSigma_
@SystemSigma_ 8 ай бұрын
Great sum up, good job. Still, I would say that modern C++, by trying to fix some language inconsistency (like this) just adds more confusion to an already complex language. And people (engineers) want to solve problems, not relearning the same programming language every 2 years (which of course does not happen). Mixing old practices with new language versions is what creates chaos in codebases that want to stay up to date with the latest language features.
@CodeForYourself
@CodeForYourself 8 ай бұрын
Yeah, I hear what you’re saying. I think about it a bit differently though. Backwards compatibility is a feature of a language and influences the design of the language as a whole. It does mean that some features will become confusing but it is exactly this that is one of the reasons C++ is so popular. I for one am happy that there are new features added into the language, they make C++ a much more pleasant language to use. That being said, you’re not alone in your sentiment. Which, the way I see it, is the reason for existence of Rust, cppfront and Carbon languages.
@Megalcristo2
@Megalcristo2 6 ай бұрын
There are 2 situations that come up in my mind: 1: You have a big vector filled with "Bar"s, this Bar is a class that have a very expensive to construct member, let's call it "local_v", normally you would construct local_v within Bar constructor, but you notice only a few instances of Bar use local_v, why am I creating such an expensive member unconditionally if only a few instances are going to use it? If you know which instance uses this local_v member at the time you are instantiating them, you can use an overload constructor and make local_v optional, but if you don't know which one is going to make use of this member, you can create a getter function with a local static variable that receive it value from a lambda that it is called immediately and creates that local_v, and then you just return that local variable 2: When you have a function that is called multiple times, and you want a global variable to keep track of the changes, but you don't have access to the caller function (maybe it's from an external program you don't have access to)
@CodeForYourself
@CodeForYourself 6 ай бұрын
Yeah, both are valid use-cases, although I would still say that these use-cases are a bit fringe ones. For case 1. you say yourself that we could make the local_v optional and superficially, I think I would prefer this solution as it indicates the intent of local_v being, well, an optional value. When reading such code I will have less things to think about. As for case 2. I would probably wrap such a call into a class with a static variable so that things are contained within that class. That is if I absolutely cannot avoid such a situation and make the changes local instead. Hope it makes sense. Or did I get it wrong? If you'd like to chat more about it, can I please ask you to write up a small example on godbolt.org and paste a link here? This way we can communicate directly in code without typing out code in youtube comments 😅
@theevilcottonball
@theevilcottonball 7 ай бұрын
Would you also advise against marking functions as static in a "unity build" i. e. a single compilation unit? What about using static to mark that an array as a function argument should have a minimum number of elements e.g. `void itoa(int32_t number, char[static 33], int32_t base)` or that a pointer is nonnull `T[static 1]`? (Not sure whether C++ supports this) What if you want to use your library from C and so you cannot use an unnamed namespace for functions and global variables that need internal linkage?
@CodeForYourself
@CodeForYourself 7 ай бұрын
Thanks for all these questions. All of these suggestions come from the assumption that we use pure C++. When we have to guarantee interoperability with C things will be different. As for unity I won’t be able to tell as i don’t have enough experience with it.
@xBiggs
@xBiggs 7 ай бұрын
I generally always use static in combination with constexpr
@CodeForYourself
@CodeForYourself 7 ай бұрын
Well, then I guess that’s exactly the point of the video, there is very little reason to do so if we are using unnamed namespaces rigorously.
@xBiggs
@xBiggs 7 ай бұрын
@@CodeForYourself I don't understand you reasoning. If I want to force the compiler to make a compile time constant, I will need to use static/inline constexpr. Constexpr alone is not enough
@xBiggs
@xBiggs 7 ай бұрын
@@CodeForYourself I think inside namspaces inline constexpr makes sense. Inside functions static constexpr makes sense.
@CodeForYourself
@CodeForYourself 7 ай бұрын
@@xBiggs well, I think your statements are a bit too general. If you mark your variable as constexpr in an unnamed namespace it will be a compile-time constant, for example. So in this situation, constexpr is enough. Basically, whenever you are creating a constexpr variable at namespace scope it *should* be created at compile time. As for "inside functions" if you *need* a static variable there, i.e., the one that lives beyond your function, then sure, use static for this. But in most cases, you're better off with a constant at a namespace scope. Or am I misunderstanding what you're saying? Please feel free to provide an example on godbolt.org to illustrate what you mean if I misunderstand.
@xBiggs
@xBiggs 7 ай бұрын
@@CodeForYourself Marking an automatic variable as constexpr does not guarantee that it will be computed at compile time. Marking it static constexpr will guarantee compile time computation. You could choose to do this at file or namespace scope (without redundant static), but then that variable would have increased visibility. Many constants are only used in the context of one operation so the increased visibility does not make sense and pollutes the namespace. Static constexpr does not make sense at file/namespace scope because it is redundant as you said. Inline constexpr is desired here for ODR purposes
@u9vata
@u9vata 7 ай бұрын
Honestly when doing performance optimizations on cross-platform code I very regularily use static in modern C++ codebases. Its much stronger hint to the compiler to inline a function and you do not need to rely on force_inline macros and also you still leave some leeway for the compiler to decide otherwise. In many circumstances on average this is best choice. Also please think about libraries that you write to be used from C and C++ too: many times best to use static there (outside classes). Also in the local scope its actually too a very cool little optimization to mark a temporary little variable (like a string or vector) as static and every call to the function you resize that to the "proper" size. When the function is called bunch of times, this can save you a lot of runtime cost because of MUCH less allocations. Of course one should branch on the allocated amount and max out over some size when you overwrite the static with a small, newly created object and thus leave the memory leak behind. This is a much easier way to quickfix performance oriented issues than refactoring to custom allocators and leads to much more maintanable code honestly. If threading gets involved, you can use static thread_local, but regular static is fine unless there is threading. I not only think there is place to use static outside classes in modern C++, but I honestly apply it in part of my modern style for both use cases - especially when doing performance oriented codes.
@CodeForYourself
@CodeForYourself 7 ай бұрын
These are all valid points and I agree that the statement is a bit controversial. There is probably no one solution that fits everyone. If you do need to call your code from C you will be optimizing different things for sure. But this is not who this video here targets. As for the performance concerns, the way you describe using static locally seems to be very similar to just using a global. My preference would be still to avoid globals. We could pass this vector from your example as a parameter to a function and change it there, which in my opinion is already a bit better as it shows the intent slightly better. I guess my main problem with the local static is that if we agree to allow such global objects hidden away into functions we make it less clear what happens on the caller side. In my experience this becomes hard to maintain over time and keep clear from bugs. But then again, we are talking about personal experiences here. The aim of this talk is to give a “good enough” guidance to the beginners. I am confident that most of the things to be done can be done without static (outside classes). Once we get to squeezing the last possible performance out of the program there are sometimes tricks we have to employ. But these problems occur at a much later stage when people usually have much more knowledge baggage to tackle them.
@u9vata
@u9vata 7 ай бұрын
@@CodeForYourself I see your point, just not agree for my use cases where I find these much better 🙂 > We could pass this vector from your example as a parameter to a function and change it there + > I guess my main problem with the local static is that if we agree to allow such global objects hidden away into functions we make it less clear what happens on the caller side. In my experience this becomes hard to maintain over time and keep clear from bugs I actually think this approach of sending in a vector is more bad for bugs! Why? You say the static local variable is like a global one. In storage yes - but in scope no! Also if you think about it: Making API such a way that the caller provides the vector to use makes more possible mistakes and is less self-contained, localized code. Also ownership becomes not clear and needs to be documented. I am not saying its a hugely bad alternative solution, but it has two issues: Less locality of the code (thus less good maintenance - as for good maintenance you want things as local as possible) and the other issue is that this solution takes a bigger refactor to the codebase - also possibly breaking binary compatibility of your library if this is a library. The solution I advise has none of those issues and you might argue that when someone first see this "pattern", that is new to them, but I used to comment over these kind of static (thread)locals with // JHapak's pattern... That makes people ask either me or the project docs about what it is and its very easy to get the hang of this. > Once we get to squeezing the last possible performance out of the program there are sometimes tricks we have to employ. Things is that honestly this is not "last possible performance" like a thin margin, but I know cases when just this sped up an application 300x in certain algorithms related to parsing little strings. So much so that I really would advise teachers to literally teach this lesser known thing as pattern in programming. I literally prefer this solution to all the custom allocator style solutions and it is very handy to know about this pattern. Also worth mentioning that maybe it would be nice to have compiler / language support for this pattern - beause currently its hard to abstract this kind of working out (like "JPTR_string" wrapper around string) without using macros. That is I mean "by using a separate local or each call location" - because shared one is easy to do. I wish there would be something in the language to support this construction more, because using it very often. > But these problems occur at a much later stage when people usually have much more knowledge baggage to tackle them. First I wanted to argue with the "later stage" by saying that in my experience its very hard to first just write the code and only AFTER optimize it (will lead to very bad performance because architecture is not optimization friendly) - but I see that you mean later as in someone life / experience with C++. But I honestly think its not that hard to know what static is useful for this situation and this situation also teaches well what static means in that local scope use case well. Also if you think about it: using C++ is already a premature optimization (a very good one - I believe its very huge misconception to think optimizing on the go is bad) and thus for stuff that do not need these kind of things maybe the language was a bad choice anyways so I expect people to know what this static means in those circumstances. It is happening a lot in our codes because I choose not to abstract it behind some macro. Then a newcomer would come and if they learned from someone this is not to be used they would all the time create performance regressions (or code review work) which is not good.
@CodeForYourself
@CodeForYourself 7 ай бұрын
@@u9vata I don't fully agree with your suggestions, but I think it is hard to argue about the particulars here. You clearly have some good experience using static in the way you describe, while I have experience nearing the opposite. Considering how versatile C++ is, probably both arguments hold, depending on the concrete design. I definitely don't feel comfortable telling that "not using static is the only way to go", but it *is* the way I had very good experience with over my decade+ long professional usage of C++, in safety critical systems among others, so I do feel comfortable recommending it as a guiding principle. One thing I already mentioned in another discussion under this video is that static data makes things much harder to test, especially if we want to run tests in parallel. But this is just one data point. I feel that if we stay at very general level we will get stuck in a local maximum of our conversation along the lines of "my experience is" and I don't believe that this is a good place to start a meaningful conversation from. As an alternative, we could find a concrete example and share it on godbolt.org, quick-bench.com, and cppinsights.io digging into particulars and figuring out why we see a certain behavior. Thoughts?
@u9vata
@u9vata 7 ай бұрын
@@CodeForYourself I am not sure if microbenchmarks helps in this case, just generally found that saying "inline" usually is more often not inlined than inlined. Maybe this you can test with some godbolt scenarios. For the others there is literally no need for godbolt - for the static in local scope the langauge definition describes already what I want so is realiable. In case of the static inline functions outside classes being more easily inlined I think that is a compiler hint currently and I just only hope they keep it that way. In any ways. I suggest writing inline asdf() {....something } in various cases and see how many times it is indeed inlined - you will likely get surprised how many times its not inlined and when you do perf that is what you want. The static linkage helps here because the compiler knows this function can never be called outside of the current compilation unit so it more easily inlines them without generating function code. I also use this kind of static inline codes in header-only libraries. There was actually even a case (I can't recall what was the code) where it was the only solution or goto - ended up actually writing goto as it was more performant (less instruction cache clutter). I think for the case when we talk about static functions outside classes - that is useful because its how it turned out to be, but if I would design my language I would likely not do it this way. But for the static keyword in local scope in function bodies its a good pattern (maybe a bad name for the keyword one can argue though).
@CodeForYourself
@CodeForYourself 7 ай бұрын
@@u9vata I fully agree that inline does not really control inlining much. I also agree with you that putting functions in such a place where the compiler can reason about them easier makes its work of optimizing stuff away easier. So, static with its superpower to make functions have internal linkage, helps for sure. That being said, I've had pretty good milage out of the link time optimization (LTO), which partially mitigates the issue. I also agree that you might get some performance benefits of putting static within functions, but I really dislike this pattern from readability and testability perspective, so I would only do it if I need to for performance and even then I would think and measure twice. Even in the core guidelines, they explicitly mention the case you are talking about in not too favorable terms: isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#cp1-assume-that-your-code-will-run-as-part-of-a-multi-threaded-program, also here: isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#cp2-avoid-data-races. In the end of the day, most of the code is not living on the critical path, so I'd argue that readability is more important for the vast majority of the code. 🤷
@incodewetrust8862
@incodewetrust8862 8 ай бұрын
I was building a curl application, so using static inside class all class memmbers that would be used by curl built in functions i had to make all class members static too, the only way was to bring them outside class
@CodeForYourself
@CodeForYourself 8 ай бұрын
I don't fully understand your setup. Could you maybe elaborate?
@matthewmurrian9584
@matthewmurrian9584 7 ай бұрын
Generating a lookup table that requires non-constexpr functions to calculate, for example, is still a completely reasonable time to use a static variable.
@CodeForYourself
@CodeForYourself 7 ай бұрын
I’m not sure it is guaranteed that a static variable will be initialized at compile time. Although I’m not 100% sure.
@matthewmurrian9584
@matthewmurrian9584 7 ай бұрын
@@CodeForYourself I didn't say they would. Nothing of the sort. I am responding to your claim that there are no justifiable uses of static variables at the function level in modern C++. There absolutely are. One example: Lookup tables or similar "static" data that must be calculated with non-constexpr functions. It simply isn't an option to calculate them at compile-time. They cannot be constexpr. But they are static data and should only be calculated once. Hence, static storage initialized at runtime. A related example: Lookup tables that are larger than you want to store in your binary. It is quite common (in fact, the whole point) to pre-compute lookup tables to trade performance for memory usage. Lookup tables are generally large in-memory but are computed from "small" formulas. 'constexpr all the things' doesn't mean everything that can be computed at compile-time _should_be_. Or, maybe it does... but that's wrong.
@ishi_nomi
@ishi_nomi 7 ай бұрын
Good point. But as a programmer mainly needed to program in pure c, may I say that this suggestion only work in c++? I think in c we need to use static for encapsulation among different compile unit (since there are no namespace).
@CodeForYourself
@CodeForYourself 7 ай бұрын
Yeah, this recommendation is for C++ only. I’m not a huge expert on C, so I can’t give a confident advice there
@RandomGeometryDashStuff
@RandomGeometryDashStuff 8 ай бұрын
13:41 so if you #include header file that has inline keyword, you use inline in source file (that has the #include)
@CodeForYourself
@CodeForYourself 8 ай бұрын
That should be ok. What I meant is “don’t *write* inline in the source file”. If it gets there through a header file it’s ok. Does this make sense?
@RandomGeometryDashStuff
@RandomGeometryDashStuff 8 ай бұрын
04:58 errno?
@CodeForYourself
@CodeForYourself 8 ай бұрын
Sorry, I don’t get what you mean. Care to elaborate?
@RandomGeometryDashStuff
@RandomGeometryDashStuff 8 ай бұрын
​@@CodeForYourselferrno is global not const variable(ish) that is set by many functions like execve (ish): errno is not actually variable on glibc on linux: # define errno (*__errno_location ())
@CodeForYourself
@CodeForYourself 8 ай бұрын
@@RandomGeometryDashStuff ok sure, but what of it? What is the statement that you are making here?
@xBiggs
@xBiggs 7 ай бұрын
@@RandomGeometryDashStuff I don't think C++ people use errno like C people do
@RandomGeometryDashStuff
@RandomGeometryDashStuff 7 ай бұрын
​@@xBiggshow else to get which error happened in recv (example)?
@ABaumstumpf
@ABaumstumpf 7 ай бұрын
"Use unnamed namespace instead of static cause it has more super-powers" ? No actual reason as to why? Same with the static member in functions - no reason why not to use them?
@CodeForYourself
@CodeForYourself 7 ай бұрын
Ok, I see your point. The reason unnamed namespace is more powerful is that it also works with classes. If you define a class in an unnamed namespace it will also have internal linkage. So it is simply a more universal way of forcing internal linkage on stuff. As for “static member in functions” I don’t really know what you mean. If you mean marking a local object in a function as static then I think I did give some reasoning. It mostly gets used to return such a value out of a function, which basically makes it a kind of a global variable.
@ABaumstumpf
@ABaumstumpf 7 ай бұрын
@@CodeForYourself having static variables can be used for many thing - we use it extensively for quasi-global configurations that can change at runtime. There needs to be a way to access them, some of them might never be used - what other way is there than quasi-global variable via a function? None that is anywhere close to in terms of ease-of-use, safety, or performance. And global namespace also being able to define classes - then we should use Goto instead of For-loops.
@CodeForYourself
@CodeForYourself 7 ай бұрын
@@ABaumstumpf I know that sometimes it makes sense to store configurations as a form of Mayer’s singleton, i.e., returning a static object from a function. It does provide a benefit of initializing the object on demand exactly once. However in my experience, I prefer passing the configurations around as a const ref instead if this makes any sense. As for your comment about goto statements - I don’t understand what you mean. What is it that you’re saying here?
@ABaumstumpf
@ABaumstumpf 7 ай бұрын
@@CodeForYourself "I prefer passing the configurations around as a const ref instead if this makes any sense." That procludes you from any changes or reloading of the config outside from the single-point of truth and limits the ability to replicated or swap the config - or you have to deal with lifetimes your self again. "As for your comment about goto statements - I don’t understand what you mean. What is it that you’re saying here?" On the question of why we should use anonymous namespaces over the more concise static you said cause it is more powerful - by that logic you must also advocate for the more powerful goto over simple loops.
@CodeForYourself
@CodeForYourself 7 ай бұрын
@@ABaumstumpf I disagree with both statements here. With the configs, I can still make changes, I just have to be more explicit as to where and when they are made. In my experience this is less error prone. If you have them as mutable quasi global objects you give away control over them and also make your code much harder to test. As for the goto statement, that is your logic not mine. Goto statements are very error prone, that’s why we don’t use them anymore. I also fail to see how static is more concise. If you have two or more static variables you already have to do more typing than if you just put things in an unnamed namespace. And sometimes structs and classes should have internal linkage, what would you suggest to do with those other then put them into an unnamed namespace?
Use static in classes (when needed) in modern C++
16:34
Code for yourself
Рет қаралды 1,2 М.
Re-inventing move semantics in modern C++ in 13 minutes
13:20
Code for yourself
Рет қаралды 6 М.
🌊Насколько Глубокий Океан ? #shorts
00:42
C++ Weekly - Ep 382 - The Static Initialization Order Fiasco and C++20's constinit
12:39
C++ Weekly With Jason Turner
Рет қаралды 12 М.
Using CMake Dependencies Effectively (aka like a Ninja)
21:10
Const correctness in C++
10:02
Code for yourself
Рет қаралды 2,7 М.
WHY IS THE STACK SO FAST?
13:46
Core Dumped
Рет қаралды 139 М.
Async Rust Is A Bad Language | Prime Reacts
28:46
ThePrimeTime
Рет қаралды 89 М.