Weβll talk about eight handy changes that affect your everyday code. Four changes concern the language itself, and four more - its standard library.
You may also be interested in the article Ten C ++ 11 Features, which every C ++ developer should use
I took some examples from the reports at conferences of the Russian C ++ User Group - for this many thanks to its organizers and speakers! I took examples from:
It is convenient to decompose std::pair
, std::tuple
and structures using the new syntax:
#include <string> struct BookInfo { std::string title; // In UTF-8 int yearPublished = 0; }; BookInfo readBookInfo(); int main() { // title year, auto [title, year] = readBookInfo(); }
In C ++ 17, there are restrictions on decomposition when declaring:
auto [title, [header, content]] = ...
Decomposition in the declaration, in principle, can decompose any class - it is enough to write a hint once by specializing tuple_element
, tuple_size
and get
. Read more in the article Adding C ++ 17 structured bindings support to your classes (blog.tartanllama.xyz)
Decomposition during declaration works well in std::map<>
and std::unordered_map<>
containers with the old .insert()
method and two new methods:
An example of a decomposition with try_emplace and a key-value decomposition when traversing a map:
#include <string> #include <map> #include <cassert> #include <iostream> int main() { std::map<std::string, std::string> map; auto [iterator1, succeed1] = map.try_emplace("key", "abc"); auto [iterator2, succeed2] = map.try_emplace("key", "cde"); auto [iterator3, succeed3] = map.try_emplace("another_key", "cde"); assert(succeed1); assert(!succeed2); assert(succeed3); // key value range-based for for (auto&& [key, value] : map) { std::cout << key << ": " << value << "\n"; } }
Key rules:
std::make_pair
no longer needed: feel free to write std::pair{10, "hello"s}
expressions, the compiler itself will output the typestd::lock_guard<std::mutex> guard(mutex);
become shorter: std::lock_guard guard(mutex);
std::make_unique
and std::make_shared
are still neededYou can create your own hints for automatic display of template parameters: see Automatic_deduction_guides
An interesting feature: the constructor from initializer_list<>
skipped for a list of one element. For some JSON libraries (such as json_spirit) this can be fatal. Do not play with recursive types and STL containers!
#include <vector> #include <type_traits> #include <cassert> int main() { std::vector v{std::vector{1, 2}}; // vector<int>, vector<vector<int>> static_assert(std::is_same_v<std::vector<int>, decltype(v)>); // assert(v.size() == 2); }
Avoid nesting of namespaces, and if not avoided, then declare them like this:
namespace product::account::details { // ... ... }
Key rules:
[[fallthrough]]
attribute or the break;
instruction break;
[[nodiscard]]
for functions returning an error code or owning a pointer (whether smart or not)[[maybe_unused]]
for variables that are only needed for checking in assertFor more information about attributes, see the article How to use attributes from C ++ 17 . There will be brief excerpts.
In C ++, you have to add break after each case to the switch constructs, and even an experienced developer can easily forget about this. The fallthrough attribute comes to the rescue, which can be pasted to an empty instruction. In fact, the attribute is attached to the case following the empty instruction.
enum class option { A, B, C }; void choice(option value) { switch (value) { case option::A: // ... case option::B: // warning: unannotated fall-through between // switch labels // ... [[fallthrough]]; case option::C: // no warning // ... break; } }
To take advantage of the attribute, the warning -Wimplicit-fallthrough
should be included in GCC and Clang. After enabling this option, each case that does not have a fallthrough attribute will generate a warning.
In projects with high performance requirements, you can practice avoiding the emission of exceptions (at least in some components). In such cases, an operation execution error is reported by the return code returned from the function. However, it is very easy to forget to check this code.
[[nodiscard]] std::unique_ptr<Bitmap> LoadArrowBitmap() { /* ... */ } void foo() { // warning: ignoring return value of function declared // with warn_unused_result attribute LoadArrowBitmap(); }
If you use, for example, your own class of errors, you can specify the attribute once in its declaration.
class [[nodiscard]] error_code { /* ... */ }; error_code bar(); void foo() { // warning: ignoring return value of function declared // with warn_unused_result attribute bar(); }
Sometimes programmers create a variable that is used only in the debug version to store the error code of the called function. Perhaps this is just a code design error, and the return value should always be processed. However:
// ! ! auto result = DoSystemCall(); (void)result; // unused variable assert(result >= 0); // [[maybe_unused]] auto result = DoSystemCall(); assert(result >= 0);
Rules:
const string&
try to accept non-possessing string_view
by valuestring
, as beforeFor more information on why string_view is best applied only to parameters, see std :: string_view is constructed from temporary instances of strings.
The string_view
class string_view
good because it is easily constructed from both std::string
and const char*
without additional memory allocation. It also has constexpr support and repeats the std :: string interface. But there is a minus: for string_view
presence of a null character at the end is not guaranteed.
The use of optional<>
and variant<>
so wide that I will not even try to fully describe them in this article. Key rules:
optional<T>
instead of unique_ptr<T>
for the composition of an object T whose lifetime is shorter than the ownerβs lifetimeunique_ptr<Impl>
, because the definition of Impl is hidden in the class implementation fileoptional
for error handling: it does not carry any information about the errorExpected<Value, Error>
, based on boost::variant<...>
Sample code with optional:
// nullopt - nullopt_t, // optional ( nullptr ) std::optional<int> optValue = std::nullopt; // ... optValue ... // , -1 const int valueOrFallback = optValue.value_or(-1);
operator*
and operator->
, as well as the convenience method .value_or(const T &defaultValue)
operator*
, throws an exception std::bad_optional_access
when there is no valuestd::nullopt
less than any valid valueExample code with variant: here we use variant to store one of several states in the case when different states may have different data
struct AnonymousUserState { }; struct TrialUserState { std::string userId; std::string username; }; struct SubscribedUserState { std::string userId; std::string username; Timestamp expirationDate; LicenseType licenceType; }; using UserState = std::variant< AnonymousUserState, TrialUserState, SubscribedUserState >;
The variant advantage in its approach to memory management: data is stored in fields of type variant type without additional allocations of memory. This makes the size of the variant type dependent on the types that make it up. So a size table on 32-bit processors may look like (but this is not accurate):
std::vector<>
&text[0]
, but it has undefined behavior on empty lines.It may be better to rely on the GSL library (C ++ Core Guidelines Support Library) for byte manipulations.
Key rules:
std::filesystem::path
instead of strings in all parameters that imply the pathcanonical
function: perhaps you meant the lexically_normal methodrelative
function: perhaps you meant lexically_relativeWhat is bad boost :: filesystem? It turns out that he has several design problems:
Any experienced programmer knows the difference in processing paths between Windows and UNIX systems:
Of course, filesystem abstracts from such differences and makes it easy to work with both platform-specific strings and universal UTF-8:
std::filesystem::path
constructor from std::string
- on Windows, the constructor considers the OS encoding as the input encoding!The std :: clamp function complements the min and max functions. It cuts off the meaning from above and below. A similar function boost::clamp
is available in earlier versions of C ++.
The rule "do not reinvent the clamp" can be summarized: in any large project, avoid duplicating small functions and expressions for rounding, clipping values, etc. - just add it to your library once.
A similar rule works for string processing tasks. Do you have your own little library for strings and parsing? Does it have parsing or number formatting? If so, replace your implementation with calls to_chars and from_chars
The to_chars
and from_chars
support error handling. They return two values:
char*
or const char*
respectively, and points to the first code unit (i.e. char or wchar_t) that could not be processedstd::error_code
and reports detailed error information suitable for throwing an exception std :: system_errorSince in the application code, the error response method may differ, you should place the to_chars and from_chars calls inside your libraries and utility classes.
#include <utility> // , 0 // ( atoi, ) template<class T> T atoi_17(std::string_view str) { T res{}; std::from_chars(str.data(), str.data() + str.size(), res); return res; }
Source: https://habr.com/ru/post/343622/
All Articles