当前位置:网站首页>Tips for this week 140: constants: safety idioms
Tips for this week 140: constants: safety idioms
2022-07-07 17:58:00 【-Flying crane-】
As TotW#140 Originally published in 2017 year 11 month 8 Japan
from Matt Armstrong A literary creation
stay C++ What is the best way to express constants in ? You may know the meaning of this word in English , But it's easy to mistakenly express this concept in code . ad locum , We will first define some key concepts , Then list security technologies . For curious people , We will learn more about the possible problems later , And describe C++17 Language function , This function makes it easier to express constants .
“C++ Constant ” There is no formal definition , So let's propose an informal definition .
- value : The value never changes ; Five or five . When we want to express a constant , We need a value , And there is only one .
- object : At every point in time , Object has a value .C++ Great emphasis is placed on the variability of objects , But constant changes are not allowed .
- name : Naming constants is more useful than literal constants . Variables and functions can be evaluated as constant objects .
in summary , Let's call constants variables or functions that always evaluate to the same value . Here are a few key concepts .
- Secure initialization : Many times constants are expressed as values in static storage , Must be safely initialized . For more information , see also C++ Style guide .
- link : Links and how many instances of named objects are in the program ( or “ copy ”) of . Usually, it is better to have only one constant name for a single object in the program . For global or namespace scoped variables , This involves external links ( You can read more about links here ).https://en.cppreference.com/w/cpp/language/storage_duration
- Compile time evaluation : If you know the value of constants at compile time , Sometimes compilers can better optimize code . This benefit sometimes proves that it is reasonable to define the value of constants in the header file , Although it will increase complexity .
When we say we “ Add a constant ” when , We are actually declaring a API And define its implementation in a way that meets most or all of the above criteria . Language doesn't stipulate how we do this , Some methods are better than others . Usually the easiest way is to declare a const or constexpr Variable , If it is in the header file , Is marked as inline . Another way is to return a value from a function , This method is more flexible . We will introduce examples of these two methods .
About const Explanation : It is not enough . One const Object is read-only , But this does not mean that it is immutable , It doesn't mean that its value is always the same . The language offers changes that we think are const The value of the method , for example mutable Key words and const_cast. But even simple code can prove this :
void f(const std::string& s) {
const int size = s.size();
std::cout << size << '\n';
}
f(""); // Prints 0
f("foo"); // Prints 3
In the code above ,size It's a const Variable , But it saves multiple values while the program is running . It is not a constant .
Constant in header file
All idioms in this section are robust and worthy of recommendation .
An inline constexpr Variable
from C++17 Start , Variables can be marked inline , Make sure that there is only one copy of the variable . When and constexpr Used together to ensure safe initialization and destruction , This provides another way to define constants , The value of this constant can be accessed at compile time .
// in foo.h
inline constexpr int kMyNumber = 42;
inline constexpr absl::string_view kMyString = "Hello";
One extern const Variable
// Declared in foo.h
ABSL_CONST_INIT extern const int kMyNumber;
ABSL_CONST_INIT extern const char kMyString[];
ABSL_CONST_INIT extern const absl::string_view kMyStringView;
The above example declares an instance of each object . extern Keywords ensure external links . const Keywords help prevent unexpected mutations in values . This is a good method , Although it does mean that the compiler cannot “ notice ” Constant values . This limits their practicality to a certain extent , But it's not important for typical use cases . It also needs to be in the associated .cc Variables defined in file .
// Defined in foo.cc
const int kMyNumber = 42;
const char kMyString[] = "Hello";
const absl::string_view kMyStringView = "Hello";
ABSL_CONST_INIT Macros ensure that each constant is initialized at compile time , But that's it. . It does not make the variable const, Nor will it prevent the use of nontrivial destructors that violate the rules of the style guide to declare variables . See the reference to macros in the style guide .
You may want to use constexpr stay .cc Variables defined in file , But at present, this is not a portable method .
Be careful : absl::string_view Is a good way to declare string constants . This type has a constexpr Constructor and a normal destructor , Therefore, it is safe to declare them as global variables . Because the string view knows its length , So using them does not require runtime calls strlen().
One constexpr function
No parameters constexpr The function will always return the same value , So it is used as a constant , And can usually be used to initialize other constants at compile time . Because all constexpr Functions are implicitly inline , So there is no link problem . The main disadvantage of this method is that constexpr Limitations of code in functions . secondly ,constexpr yes API An important aspect of the contract , It has practical consequences .
// in foo.h
constexpr int MyNumber() {
return 42; }
An ordinary function
When constexpr When the function is not desirable or feasible , You can choose ordinary functions . The function in the following example cannot be constexpr, Because it has a static variable :
inline absl::string_view MyString() {
static constexpr char kHello[] = "Hello";
return kHello;
}
Be careful : Make sure to use static... When returning array data constexpr specifier , for example char[] character string 、absl::string_view、absl::Span etc. , To avoid subtle mistakes .
A static class member
Suppose you are already using a class , Then static members of classes are a good choice . These always have external links .
// Declared in foo.h
class Foo {
public:
static constexpr int kMyNumber = 42;
static constexpr char kMyHello[] = "Hello";
};
stay C++17 Before , Must also be in .cc The file provides definitions for these static data members , But for both static and constexpr Data members of , These are unnecessary now ( And has been discarded ).
// Defined in foo.cc, prior to C++17.
constexpr int Foo::kMyNumber;
constexpr char Foo::kMyHello[];
It's not worth introducing a class just to act as the scope of a bunch of constants . Please use other technology instead .
Discouraged alternatives
#define WHATEVER_VALUE 42
There is no reason to use a preprocessor , Please refer to the style guide .
enum : int {
kMyNumber = 42 };
The enumeration technique used above is reasonable in some cases . It produces a constant kMyNumber, Will not lead to the problems discussed in this tip . But the alternatives already listed will be more familiar to most people , Therefore, it is usually preferred . Use enumeration when enumeration itself makes sense ( for example , See Tips #86 “ Use class enumeration ”).
The method that takes effect in the source file
All the above methods also apply to a single .cc file , But it may be too complicated . Because constants declared in the source file are only visible in that file by default ( See internal link rules ), So the simpler way , For example, definition constexpr Variable , Usually effective :
// within a .cc file!
constexpr int kBufferSize = 42;
constexpr char kBufferName[] = "example";
constexpr absl::string_view kOtherBufferName = "other example";
Above in .cc The document is very good , But not in the header file ( See warnings ). Read it again and remember it . I will explain the reason as soon as possible . Cut a long story short : stay .cc Variables defined in file constexpr Or declare them as extern const .
In the header file , Please note that
Unless you pay attention to the idioms explained above , otherwise const and constexpr Objects may be different objects in each translation unit .
It means :
- error : Any code that uses constant addresses will have errors , Even terrible “ No behavior defined ”.
- inflation : Each translation unit, including the title, has its own copy . It's no big deal for simple things like primitive numeric types . Not very good for strings and larger data structures .
Within the namespace ( That is, not in a function or class ) when ,const and constexpr Objects have implicit internal links ( The same links for unnamed namespace variables and static variables that are not in functions or classes ). C++ The standard guarantees that each translation unit that uses or references an object will get a different object “ copy ” or “ Instantiation ”, Each is located at a different address .
In a class , You must additionally declare these objects static , Otherwise, they will be instance variables that cannot be changed , Instead of unchangeable class variables shared between each instance of the class .
Again , In the function , You must declare these objects as static objects , Otherwise, they will take up stack space and construct each time the function is called .
An example error
that , Is this a real risk ? Please consider it :
// Declared in do_something.h
constexpr char kSpecial[] = "special";
// Does something. Pass kSpecial and it will do something special.
void DoSomething(const char* value);
// Defined in do_something.cc
void DoSomething(const char* value) {
// Treat pointer equality to kSpecial as a sentinel.
if (value == kSpecial) {
// do something special
} else {
// do something boring
}
}
Please note that , This code will kSpecial First of all char The address of value Compare as a magic value of a function . You sometimes see code doing this to shorten the complete string comparison .
This can lead to a subtle mistake . kSpecial An array is constexpr, This means that it is static ( have “ Inside ” link ). Although we think kSpecial yes “ A constant ”—— In fact, it's not —— It is a family of constants , One for each translation unit ! Yes DoSomething(kSpecial) The call to seems to do the same thing , But the function takes different code paths according to the location where the call takes place .
Any code that uses an array of constants defined in the header file , Or use the code of constant address defined in the header file , Is enough to solve this mistake . Such errors usually occur in string constants , Because they are the most common reason to define arrays in header files .
An example of undefined behavior
Just adjust the example above , And will DoSomething Move to the header file as an inline function . Bingo: Now we have undefined behavior , or UB. The language is required in each translation unit ( Source file ) All inline functions are defined in exactly the same way in —— This is the language “ A defining rule ” Part of . This particular DoSomething The implementation refers to a static variable , So each translation unit is actually right DoSomething The definition of is different , So it's undefined behavior .
Irrelevant changes to program code and compiler may change inline decisions , This may cause such undefined behavior to change from benign behavior to wrong .
Will this cause problems in practice ?
Yes . In one of the practical mistakes we encountered , The compiler can determine in a specific translation unit ( Source file ) in , Only the large static defined in the header file is partially used const Array . It doesn't emit the entire array , Instead, it optimizes the unused parts it knows . One way to partially use arrays is through inline functions declared in headers .
The problem is , This array is used by other translation units in this way , Static state const Arrays are fully used . For those translation units , The compiler generates an inline function version that uses a full array .
Then the linker appears . The linker assumes that all instances of inline functions are the same , Because the single definition rule stipulates that they must be the same . It discards all copies of the function , But only one copy - That's a copy with a partially optimized array .
When the code uses a variable in a way that needs to know its address , This error may occur . The technical term in this regard is “ The use of ODR”. In modern times C++ It is difficult to prevent ODR Using variables , Especially if you pass these values to the template function ( As in the above example ).
These mistakes do happen , And it is not easy to find in testing or code review . It is worth sticking to safe idioms when defining constants .
Other common errors
error #1: Not constat Constant
Common in pointers :
const char* kStr = ...;
const Thing* kFoo = ...;
above kFoo Is a pointer to a constant , But the pointer itself is not a constant . You can assign it to him , Set to NULL, etc. .
// Corrected
const Thing* const kFoo = ...;
// That 's fine
constexpr const Thing* kFoo = ...;
error #2: Unusual quantity MyString()
Consider this code :
inline absl::string_view MyString() {
return "Hello"; // Each call may return different values
}
The address of a string literal constant is allowed to change every time it is calculated , So the above method is a little wrong , Because it returns string_view Each call may have different .data() value . Although in many cases this will not be a problem , But it may lead to the above error .
Make MyString() constexpr It doesn't solve the problem , Because the language standard does not say that it does . One way to look at this is ,constexpr Function is just an inline function , It is allowed to execute at compile time when initializing constant values . At run time , It is no different from inline functions .
constexpr absl::string_view MyString() {
return "Hello"; // Each call may return different values
}
To avoid this kind of mistake , Please use static constexpr To replace .
inline absl::string_view MyString() {
static constexpr char kHello[] = "Hello";
return kHello;
}
Rule of thumb : If your “ Constant ” It's an array type , Store it in the local static of the function before returning . This fixes its address .
error #3: Non portable code
There are some modern C++ The feature has not been supported by some mainstream compilers
- stay Clang and GCC in , above MyString Static in function constexpr char kHello[] Arrays can be static constexpr absl::string_view. But it won't be Microsoft Visual Studio Chinese compiler . If portability is to be considered , Please avoid using constexpr absl::string_view, Until we start from C++17 get std::string_view type .
inline absl::string_view MyString() {
// Visual Studio refuses to compile this.
static constexpr absl::string_view kHello = "Hello";
return kHello;
}
- For extern const Variable , According to the standard C++, The following method of defining its value is valid , And in fact, it's better than ABSL_CONST_INIT preferable , But some compilers do not support this method .
// It's defined in foo.cc -- however MSVC 19 I won't support it .
constexpr absl::string_view kOtherBufferName = "other example";
As .cc In file constexpr Variable solution , You can provide its value to other files through functions .
error #4: Constants that are not properly initialized
The style guide has some detailed rules , Designed to protect us from common problems related to runtime initialization of static and global variables . When the global variable X The initialization of refers to another global variable Y when , There will be fundamental problems . How do we determine Y It doesn't depend on X Value ? Loop initialization dependencies can easily occur in global variables , Especially those variables that we think are constants .
This in itself is a rather tricky area of language . The style guide is an authoritative reference .
Consider the above links to read . Focus on constant initialization , The initialization phase can be interpreted as :
- Zero initialization . This is how to initialize other uninitialized static variables to types “ zero ” value ( for example 0、0.0、‘\0’、null etc. ) Why .
const int kZero; // this will be zero-initialized to 0
const int kLotsOfZeroes[5000]; // so will all of these
Please note that , Rely on zero initialization in C Quite popular in code , But in C++ Quite rare and niche . It is often clearer to assign explicit values to variables , Even if the value is zero , It makes us …
2. Constant initialization
const int kZero = 0; // this will be constant-initialized to 0
const int kOne = 1; // this will be constant-initialized to 1
“ Constant initialization ” and “ Zero initialization ” stay C++ Language standards are called “ initiate static ”. Both are always safe .
3. dynamic initialization
// This will be dynamically initialized at run-time to
// whatever ArbitraryFunction returns.
const int kArbitrary = ArbitraryFunction();
Dynamic initialization is where most problems occur . The style guide is in https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables Explained why .
Please note that ,Google C++ Documents such as style guides have always included dynamic initialization in “ initiate static ” In this broad category . “ static state ” The word applies to C++ Several different concepts in , This may cause confusion . “ initiate static ” Can be said “ Initialization of static variables ”, This can include runtime calculations ( dynamic initialization ). Language standards vary 、 Use the term in a narrower sense “ initiate static ”: Initialization done statically or at compile time .
Initialize memo
This is an ultra fast constant initialization memo ( Not in header file ):
constexpr Ensure safe constant initialization and safe ( Insignificant ) damage . whatever constexpr Variable in .cc When defined in the file, it is completely OK , But for the reasons explained above , There is a problem in the header file .
ABSL_CONST_INIT Secure constant initialization . And constexpr Different , It doesn't actually make the variable const, Nor is it guaranteed that the destructor is trivial , Therefore, you must still be careful when using it to declare static variables . Look again https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables.
otherwise , You'd better use a static variable in a function and return it . see also http://go/cppprimer#static_initialization And the one shown above “ Ordinary function ” Example .
Links for further reading and collection
https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables
http://en.cppreference.com/w/cpp/language/constexpr
http://en.cppreference.com/w/cpp/language/inline
http://en.cppreference.com/w/cpp/language/storage_duration( Link rule )
http://en.cppreference.com/w/cpp/language/ub( No behavior defined )
Conclusion
C++17 The inline variables in are not fast enough . Until then, , All we can do is use safe idioms , Let's stay away from rough edges .
We come to a conclusion , stay C++17 Language standard [lex.string] in , String literals do not need to be evaluated as identical objects from the following languages . C++11 and C++14 There are also equivalent languages in .
[lex.string] There is no language description in constexpr Different behaviors in context .
边栏推荐
- [re understand the communication model] the application of reactor mode in redis and Kafka
- Based on pytorch, we use CNN to classify our own data sets
- Tips of this week 135: test the contract instead of implementation
- viewflipper的功能和用法
- USB通信协议深入理解
- [OKR target management] value analysis
- 目标管理【管理学之十四】
- [answer] if the app is in the foreground, the activity will not be recycled?
- serachview的功能和用法
- Please insert the disk into "U disk (H)" & unable to access the disk structure is damaged and cannot be read
猜你喜欢
随机推荐
alertDialog創建对话框
ICer知识点杂烩(后附大量题目,持续更新中)
4种常见的缓存模式,你都知道吗?
深入浅出【机器学习之线性回归】
Alertdialog create dialog
USB通信协议深入理解
Mui side navigation anchor positioning JS special effect
第2章搭建CRM项目开发环境(搭建开发环境)
物联网OTA技术介绍
数学分析_笔记_第11章:Fourier级数
DatePickerDialog and trimepickerdialog
三仙归洞js小游戏源码
Chapter 3 business function development (user access project)
【4500字归纳总结】一名软件测试工程师需要掌握的技能大全
Pytorch中自制数据集进行Dataset重写
Show progress bar above window
请将磁盘插入“U盘(H)“的情况&无法访问 磁盘结构损坏且无法读取
Function and usage of numberpick
漫画 | 宇宙第一 IDE 到底是谁?
Functions and usage of ratingbar