C + + combat notes

Smart pointer

unique_ptr

unique_ptr is one of the simplest and easiest to use smart pointers. When declaring, you must specify the type with template parameters, for example:

unique_ptr<int> ptr1(new int(10));  //int smart pointer
assert(*ptr1 = 10); // Use * to get content
assert(ptr1 != nullptr);  //You can determine whether it is a null pointer

unique_ptr<string> ptr2(new string("hello")); //string smart pointer
assert(*ptr2 = "hello"); // You can use * to get content
assert(ptr2->size() == 5);  //You can use '>' to call member functions

Need special attention, unique_ Although PTR is called pointer and is used like pointer, it is not a pointer but an object. So don't try to be unique_ptr calls delete, which will automatically manage the initialized address and release resources when leaving the scope.

In addition, unique_ptr does not define addition and subtraction operations and cannot move the pointer address at will, which completely avoids dangerous operations such as pointer crossing and makes the code safer:

ptr1++;   //Cause compilation errors
ptr2 += 2;  //Cause compilation errors

Using the factory function make_unique to create unique_ptr pointer, make_unique is defined in C++14.

auto ptr3 = make_unique<int>(42);
assert(ptr3 && *ptr3 == 42);

auto ptr4 = make_unique<string>("Hello C++"); // Call the factory function to create a smart pointer
assert(!ptr4->empty());

If we use C++11, we can refer to the following code to implement a simplified version of make_unique():

template<class T, class... Args>   //Variable parameter template
std::unique_ptr<T>
my_make_unique(Args&&... args)   //Entry parameters of variable parameter template
{
    return std::unique_ptr<T>(                 //Construct smart pointer
        new T(std::forward<Args>(args)...)); //"Perfect forwarding"
}

unique_ The ownership of PTR pointer is unique and cannot be shared. Only one "person" can hold it at any time.

To achieve this, unique_ptr applies C + + transfer semantics and prohibits copy assignment, so it is transferring to another unique_ When assigning PTR, it should be noted that the ownership transfer must be explicitly declared with std::move(). After the assignment operation, the ownership is transferred, the original unique_ptr becomes null pointer, new unique_ptr takes over the ownership, thus ensuring the uniqueness of ownership:

auto ptr1 = make_unique<int>(42);  //Call the factory function to create a smart pointer
assert(ptr1 && *ptr1 == 42); //Smart pointer valid

auto ptr2 = std::move(ptr1);   //Use move() to transfer ownership
assert(!ptr1 && ptr2);   //ptr1 becomes a null pointer

Because unique_ptr prohibits copying and can only be transferred, so if you define a class, it will be unique_ptr as a member, then the class itself is not replicable. That is, unique_ptr will pass its "sole ownership" feature to its holder.

Shared pointer

shared_ PTR (shared pointer) is a better than unique_ptr is a smarter pointer.

shared_ptr<int> ptr1(new int(10));  //int smart pointer
assert(*ptr = 10);                  //At this time, the smart pointer is valid

shared_ptr<string> ptr2(new string("hello"));  //string smart pointer
assert(*ptr2 == "hello");

auto ptr3 = make_shared<int>(42); //Call the factory function to create a smart pointer
assert(ptr3 && *ptr3 = 42);

auto ptr4 = make_shared<string>("zelda");  //Call the factory function to create a smart pointer
assert(!ptr4->empty());      //Member functions can be called with '>'

Its ownership can be safely shared, that is, it supports copy assignment and can be held by multiple people at the same time, just like the original pointer.

auto ptr1 = make_shared<int>(42);    //Call the factory function to create a smart pointer
assert(ptr1 && ptr1.use_count() == 1);  //At this time, the smart pointer is valid and unique

auto ptr2 = ptr1; //Copy assignment directly without using Move
assert(ptr1 && ptr2);

assert(ptr1 == ptr2);   //shared_ptr can be compared directly
assert(ptr1.use_count() == 2);  //Are not unique and the reference count is 2
assert(ptr2.use_count() == 2);

Weak reference pointer

 shared_ The reference count of PTR will lead to a new problem, which is circular reference. This is shared_ptr is very easy to appear as a class member. A typical example is the linked list node.

class Node final
{
public:
    using this_type = Node;   //Type alias
    using shared_type = std::shared_ptr<this_type>;

public:
    share_ptr next;     //Use a smart pointer to point to the next node
};

auto n1 = make_shared<Node>();
auto n2 = make_shared<Node>();

assert(n1.use_count() == 1);   //Reference count 1
assert(n2.use_count() == 1);   //Reference count 1

n1->next = n2;  //The two nodes refer to each other, forming a circular reference
n2->next = n1;

assert(n1.use_count() == 2);  //The reference count is 2
assert(n2.use_count() == 2);  //It cannot be reduced to 0 and cannot be destroyed, resulting in memory leakage

At this point, we need to use weak_ptr. weak_ptr, as its name implies, has a very "weak" function. It is designed to break the circular reference. It only observes the pointer and does not increase the reference count, but weaks when necessary_ PTR can call the member function lock() to get shared_ptr.

In the above example, we use weak instead_ ptr.

class Node final
{
public:
    using this_type = Node;   //Type alias
    using shared_type = std::weak_ptr<this_type>;

public:
    share_ptr next;     //Use a smart pointer to point to the next node
};

auto n1 = make_shared<Node>();
auto n2 = make_shared<Node>();

assert(n1.use_count() == 1);   //Reference count 1
assert(n2.use_count() == 1);   //Reference count 1

n1->next = n2;  //The two nodes refer to each other, forming a circular reference
n2->next = n1;

assert(n1.use_count() == 1);  //Because of the use of weak_ptr, reference count is 1
assert(n2.use_count() == 1);  //Breaking the circular reference will not lead to memory leakage

if(!n1->next.expired())  //Check weak_ Is PTR valid
{
    auto ptr = n1->next().lock();  //lock get shared_ptr
    assert(ptr == n2);
}

weak_ The expired() of PTR is equivalent to shared_ Use of PTR_ Count() = = 0, used to judge weak_ Whether PTR is effective.

weak_ Another important function of PTR is to make the class create shared correctly_ ptr. Object using weak internally_ PTR to hold this pointer, then call lock() to get shared_. ptr.

This requires the auxiliary class enable_shared_from_this. Classes that need self-management must use it in the way of inheritance, and then you can use the member function shared_from_this() creates shared_ptr.

Original string

C++11 adds a new expression of "original string" to the literal quantity, with an uppercase letter R and a pair of parentheses more than the original quotation marks, as follows:

auto str = R"(nier:automata)";  //Original string

C + + strings have escape usage: add a "\" in front of them to write "\ n" "\ T" to represent non printable characters such as carriage return, skip, etc. Sometimes we don't want to escape, we just want the original form of the string.

auto str1 = R"(char""'')";  //Output as is: char ''
auto str2 = R"(\r\n\t\")";  //Output as is: \ r\n\t\“
auto str3 = R"(\\\$)";      //Output as is:\$

For the form of "quotation marks + parentheses" in the original string, C + + has also prepared a corresponding method, that is, add a special delimiter of up to 16 characters on the left and right sides of the parentheses, so as to ensure that it does not conflict with the content of the string.

For example, the following original string adds "= =" as the delimiter (or any other character sequence):

auto str = R"==(R"(xxx)")==";  //Output as is: R"(xxx)"

String conversion function

New conversion function in C++11

  • stoi()/stol()/stoll() can convert strings to integers
  • stof()/stod(), etc. can convert strings into floating-point numbers
  • to_string() can convert integers and floating-point numbers into strings
assert(stoi("42") == 42);  //String to integer
assert(stol("253") == 253L);  //String to length integer
assert(stod("2.0") == 2.0);  //String to floating point number

assert(to_string(1984) == "1984");  //Integer to string

Literal suffix

C++14 adds a literal suffix "s", which clearly indicates that it is a string type, not a C string. In this way, automatic type derivation can be used when declaring, and in other places where strings are used, the trouble of declaring temporary string variables can be saved, and the efficiency will be higher.

using namespace std::literals;  //Namespace must be opened
auto str = "std string"s;  //The suffix s represents the standard string and automatically deduces the type
assert("time"s.size() == 4);   //The standard string can call member functions directly

String view

C++17 adds a new string class string_view, which is a string view with low cost. Only one pointer and length are saved internally. It is very cheap to copy or modify.

 class string_view
{
private:
    const char* m_ptr;
    std::size_t  m_size;

public: 
    ...
};

There are four key points in this Code:

1. Because constant pointers are used internally, it is a read-only view, which can only view strings and cannot be modified. It is equivalent to "const string &", which is very safe to use.

2. Because the character pointer is used internally, it can be constructed directly from the C string without the temporary object creation operation of "const string &", so it has a wide application area and low cost.

3. Because the pointer is used, you must be careful that the referenced content may become invalid, which is similar to weak_ptr is somewhat similar, both of which are weak references

4. Because it is a read-only view, it cannot be guaranteed that the end of the string must be NULL, and the member function C cannot be provided_ Str (), that is, you can't put string_view is converted into a NULL terminated character pointer, which cannot be used to pass parameters to C functions.

Example:

string_view sv;  //Default constructor 
assert(sv.empty()); //String view is empty

sv = "fantasy"s;
assert(sv.size() == 7);  //Construct from string

sv = "c++";
assert(sv.size() == 3);  //Construct from C string

string_view sv2("std c++", 3); //Specifies the number of characters and the length of the construct
assert(sv2 == "std");

We can put string_ As a lightweight "substitute" of string, view is used in the situation of read-only and weak reference, so as to reduce the use cost of string.

When the formal parameter is string_view, you can pass in string, char *, string literal (constant)

If const string & is taken as the parameter, string literal constant and char * cannot be passed in. Only string can be used.


String formatting

C++20 adds a special format library format, which provides a special format function format(), which is similar to printf(), but uses a variable parameter template, which not only supports any number of parameters, but also realizes compile time type checking, which is very safe to use. format() no longer uses the traditional "%", but uses "{}" similar to python/c # with good readability.

format("{}", 100L); //Format output integers directly
format("{0}-{0}, {1}, {2}", "hello", 2.718, 3.14);  //The following parameters can be referenced with sequence numbers

Output:
100
hello-hello, 2.718, 3.14

The basic form of format placeholder is "{serial number: format flag}". In it, you can not only reference the following parameters with serial number (starting from 0), but also add various flags to customize the format. A brief list is as follows:

  • <: align data left
  • >: align data right
  • +: adds a sign to a number
  • -: negative numbers are marked with "-" and positive numbers are unmarked
  • Space: negative numbers are marked with "-" and positive numbers are preceded by spaces
  • b: Format as binary integer
  • d: Format as decimal integer
  • o: Format as octal integer
  • x/X: formatted as hexadecimal integer
  • #: non decimal digits display "0b", "0o", "0x" prefixes
  • Numbers: width of formatted output
format("{:>10}", "hello");  //Right justified, 10 character width
format("{:04}"), {:+04}, 100L, 88); //Specifies the fill and width, which defaults to decimal
format("{0:x}, {0:#10} ", 100L); / / formatted as hexadecimal
format("{:04o}, {:04b}", 7, 5); //Format as octal / binary, width is 4

Output:
     hello
0100, +088
64, 0X64
0007, 0101

If you want to output "{}", you need to use a special "{}}" form, for example:

format("{{xxx}}");  //Output {xxx}

regular expression

C++11 introduces regex, a regular expression library. With its powerful function, we can manipulate strings and text arbitrarily.

Regular expression is a special pattern description language, which is specially designed to deal with text. It defines a set of strict syntax rules. According to this set of rules to write patterns, complex matching, search and replacement can be realized.

Basic syntax rules:

  • .: matches any single character
  • $: end of matching line
  • ^: matches the beginning of the line
  • (): defines a subexpression that can be referenced or repeated
  • *: indicates that the element can be repeated any number of times
  • +: indicates that the element can be repeated one or more times
  • ?: Indicates that the element can be repeated 0 or 1 times
  • |: matches one of the elements on either side of it
  • []: define a character set, list a single character, define a range, or a complement of the set
  • \: escape character. Special characters match themselves after escape

C + + regular expressions mainly use two classes:

Regex: represents a regular expression, which is basic_ Specialized form of regex

smatch: indicates the matching result of regular expression. It is match_ Specialized form of results

When creating regular expression objects, we can also pass some special flags to control the processing of regular expressions. These flags are located in the namespace STD:: regex_ In constants, the following are commonly used

  • icase: case is ignored when matching, that is, case insensitive
  • Optimize: it is required to optimize the regular expression as much as possible, but it will increase the construction time of the regular expression object
  • ECMAScript: use ECMAScript compatible syntax, which is also the default syntax
  • awk/grep/egrep /: use syntax such as awk/grep/egrep.

Ignore case and optimize expression

using namespace std::regex_constants; //Open the namespace where the flag is located
regex reg1{"xyz", icase | optimize};  //Ignore case and optimize as much as possible

Regular expression algorithm:

  • regex_match: exactly matches a string
  • regex_search: find a regular match in the string
  • regex_replace: find first and then replace

Therefore, as long as we define an expression object with regex and call the corresponding matching algorithm, we can get the result immediately. However, when writing regular expressions, it is best to remember to use the original string, otherwise the escape character will be tortured.

auto make_regex=[](const auto& txt) //Regular expression creation
{
    return std::regex(txt);
};

auto make_match = []()   //Return regular matching results
{
    return std::smatch();
};

Regular matching

With the regular expression object, we can call regex_match checks the string, which returns a bool value indicating whether the target exactly matches the pattern defined in the regular expression:

auto reg = make_regex(R"(^(\w+)\:(\w+)$)"); //Use the original string to define a regular expression

assert(regex_match("a:b", reg)); //Regular matching succeeded
assert(!regex_match("a,b", reg)); //Regular matching failed

regex_match also has an overloaded form containing three parameters. If the match is successful, the result will be stored in the second parameter what, and then access the sub expression like an array:

auto str = "neir:automata"s;
auto what = make_match(); //Ready to get matching results

assert(regex_match(str, what, reg));  //Regular matching succeeded
assert(what[1] == "neir");  //First subexpression
assert(what[2] == "automata");  //Second subexpression
for(const auto &x : what){
    cout << x << ',';   //Output neir:automata,neir,automata,
}

Use regex_ During match, you should pay attention to that if you want to obtain the capture result, the target string must not be a temporary object, that is, you cannot write the face value directly. If you use the following writing method, a compilation warning will appear:

regex_match("xxx", what, reg);  //Cannot be a temporary variable such as literal value

Because the matching result needs to reference the string, and the temporary variable disappears after the function call, which will make the reference invalid.

Regular search

regex_search usage and regex_match is very similar. It does not require full-text matching. Just find a substring that conforms to the pattern. See the following example code:

auto str = "god of war"s;  //String to match
auto reg = make_regex(R"((\w+)\s(\+w))");  //Use the original string to define a regular expression

auto what = make_match();  //Ready to get matching results
auto found = regex_search(str, what, reg);  //Regular search is similar to matching

assert(found);
assert(!what.empty());
assert(what[1] == "god");  //First subexpression
assert(what[2] == "of");  //Second subexpression

Regular substitution

regex_replace, which is not to match the result, but to provide a replacement string. For example:

auto new_str = regex_replace(   //Regular replacement, return new string
    str,                        //The original string is unchanged
    make_regex(R"(\w+$)"),      //Regular expression object
    "peace"                     //Alternate text needs to be specified
);

cout << new_str << endl;   //Output god of peace

regex_replace with "^ $" can complete the "pruning" of strings

cout << regex_replace(
    "   xxx   ",
    make_regex("^\\s+"), "")   //Remove leading characters
   << endl;        //Output "xxx"

cout << regex_replace(
    "   xxx---",
    make_regex("\\-+$"), "")  //Delete the following "-"
    << endl;                  //Output "xxx"

Because the algorithm is read-only, regex_replace returns the modified new string. Making good use of this, we can take its output as the input of another function and realize functional programming in the form of "function set function":

cout << regex_replace(   //Nested regular substitution
          regex_replace(
            str,        //The original string is unchanged
            make_regex("\\w+$"),
            "peace"
          ),
          make_regex("^\\w+"),
          "godness")  //Text to replace
      << endl;  //Output goodness of peace
       

regex_ The third parameter of replace (replacement text) also follows the PCRE regular expression standard. You can use "$N" to refer to the matching sub expression and "$&" to refer to the whole matching result,

cout << regex_replace(
        "hello mike",
        make_regex(R"((\w+)\s(\w+))"),  //Regular expression object with two subexpressions
        "$2-says-$1($&)")  //Substitution refers to a subexpression
    << endl;   //Output "Mike says hello (Hello Mike)"

When using regex, we should also pay attention to the cost of regular expressions. Because regular expression objects cannot be statically checked by the C + + compiler, they will only be processed by the regular engine in the running stage, and the cost of syntax checking and dynamic compilation is very high, so we try not to create regular expression objects repeatedly, and reuse them if we can reuse them. We should pay more attention when using circulation. We must put regular expressions outside the circulation.

for(int i = 0; i < 100; ++i)
{
    auto reg = make_regex(R"(\w+)"); //Compile regular expression objects multiple times to reduce operation efficiency
}

Keywords: C++

Added by timj on Wed, 23 Feb 2022 17:20:29 +0200