17 C++11 common syntax

1, Introduction to C++11

In 2003, the C + + Standard Committee submitted a Technical Corrigendum (TC1) so that the name of C++03 has replaced the name of C++98 as the latest C + + standard before C++11. However, TC1 mainly fixes the loopholes in the C++98 Standard, and the core part of the language has not been changed. Therefore, people habitually combine the two standards as the C++98/03 Standard. From C++0x to C++11, the C + + standard has been sharpened for 10 years, and the second real standard is too late. Compared with C++98/03, C++11 has brought a considerable number of changes, including about 140 new features and the correction of about 600 defects in C++03 Standard, which makes C++11 more like a new language bred from C++98/03. In comparison, C++11 can be better used for system development and library development, with more extensive and simplified syntax, more stability and security. It not only has more powerful functions, but also can improve the development efficiency of programmers.

stagecontent
C with classesClasses and derived classes, public and private members, class construction and deconstruction, friends, inline functions, overloading of assignment operators, etc
C++1.0Add the concept of virtual function, overloading of functions and operators, references, constants, etc
C++2.0More perfect support for object-oriented, adding protection members, multiple inheritance, object initialization, abstract classes, static members and const member functions
C++3.0Further improve and introduce templates to solve the ambiguity problem caused by multiple inheritance and the treatment of corresponding construction and deconstruction
C++98The first version of C + + standard, which is supported by most compilers, has been recognized by the international organization for Standardization (ISO) and the American Institute for standardization. It rewrites the C + + standard library in the form of template and introduces STL (Standard Template Library)
C++03The second version of C + + standard has no major changes in language features, mainly including correcting errors and reducing diversity
C++05The C + + Standards Committee released a Technical Report (TR1), officially renamed C++0x, which is planned to be released sometime in the first decade of this century
C++11Many features have been added to make C + + more like a new language, such as regular expression, range based for loop, auto keyword, new container, list initialization, standard thread library, etc
C++14The extension of C++11 is mainly to repair the loopholes and improvements in C++11, such as generic lambda expression, auto return value type derivation, binary literal constant, etc
C++17Some minor improvements have been made on C++11, and 19 new features have been added, such as static_ The text information of assert () is optional, and the Fold expression is used for variable templates, initializers in if and switch statements, etc
C++20Making ing

2, List initialization

In C++98, the standard allows the use of curly braces {} to set the unified list initial value of array elements. For example:

int array1[] = {1,2,3,4,5};
int array2[5] = {0};

For some custom types, such initialization cannot be used. For example:

vector<int> v{1,2,3,4,5};

It cannot be compiled. As a result, each time you define a vector, you need to define the vector first, and then use the loop to assign the initial value to it. It is very inconvenient. C++11 expands the scope of use of the list enclosed in braces (initialization list), so that it can be used for all built-in types and user-defined types. When using the initialization list, you can add the equal sign (=) or not.

  • List initialization of built-in types
int main()
 { 
 // Built in type variable
 int x1 = {10};
 int x2{10};
 int x3 = 1+2;
 int x4 = {1+2};
 int x5{1+2};
 // array
 int arr1[5] {1,2,3,4,5};
 int arr2[]{1,2,3,4,5};
 
 // Dynamic array, not supported in C++98
 int* arr3 = new int[5]{1,2,3,4,5};
 
 // Standard container
 vector<int> v{1,2,3,4,5};
 //The equal sign can be omitted
 map<int, int>= m{{1,1}, {2,2,},{3,3},{4,4}};
 return 0;
 }
  • List initialization of custom types
class Point
{
public:
 Point(int x = 0, int y = 0): _x(x), _y(y)
 {}
private:
 int _x;
 int _y;
};
int main()
{
 Pointer p{ 1, 2 };
 return 0; 
}

2.1. How does the container support curly bracket initialization

Take vector as an example (list and other containers are similar). One constructor uses initializer_list. initializer_list is a self-defined class template. There are three main methods in this class template: the begin() iterator, the end() iterator, and the size() method to get the number of elements in the interval. It can only use curly braces for assignment.

std::initializer_list<int> list;
size_t n = list.size(); // n == 0
list = { 1, 2, 3, 4, 5 };
n = list.size(); // n == 5
list = { 3, 1, 2, 4 };
n = list.size(); // n == 4

The container supports curly bracket list initialization, which essentially adds an initializer_list constructor, initializer_list supports receiving a list of curly braces.

#include <initializer_list>
template<class T>
class Vector {
public:
 // ... 
 Vector(initializer_list<T> l): _capacity(l.size()), _size(0)
 {
 _array = new T[_capacity];
 for(auto e : l)
 _array[_size++] = e;
 }
 
 Vector<T>& operator=(initializer_list<T> l) {
 delete[] _array;
 size_t i = 0;
 for (auto e : l)
 _array[i++] = e;
 return *this;
 } 
 // ...
private:
 T* _array;
 size_t _capacity;
 size_t _size;
};

3, Derivation of variable types

3.1. Compile time type derivation: auto

Function: simplify type writing
Disadvantages: poor readability
Auto is derived according to the initialization expression type during compilation Therefore, auto is powerless to deduce the type of runtime

Using auto, you can define variables without knowing how to give the actual types, or when the types are particularly complex to write:

3.2. decltype type derivation

The premise of using auto is that the type declared by auto must be initialized, otherwise the compiler cannot deduce the actual type of auto. However, sometimes it may be necessary to deduce according to the type of the result after the expression is run, because the code will not run during compilation, and there is nothing auto can do at this time.
Therefore, decltype is the type used to define the variable based on the actual type of the expression.

int main()
{
 int a = 10;
 int b = 20;
 
 // Deduce the actual type of a+b with decltype as the type of c
 decltype(a+b) c;
 cout<<typeid(c).name()<<endl;
 return 0; 
}

void* GetMemory(size_t size) {
 return malloc(size);
}
int main()
{
 // If there are no parameters, the type of function is deduced
 cout << typeid(decltype(GetMemory)).name() << endl;
 
 // If there is a parameter list, the type of the return value of the function is deduced. Note: This is only a deduction and the function will not be executed
 cout << typeid(decltype(GetMemory(0))).name() <<endl;
 
 return 0;
}

auto and decltype do not support arguments as functions:

  1. The syntax reason is derived at compile time
  2. When compiling a function into an instruction, you need to establish a stack frame first, then you need to calculate the size of the variable, and then you need to know the type of the variable in advance

3.3. Runtime type derivation typeid

RTTI supported by C++ 98 (runtime type identification)
typeid can only view types and cannot define types with its results, because the size of its variables needs to be calculated for the establishment of a function stack frame. If it is derived at run time, the size cannot be calculated
The defect of runtime type recognition is to reduce the efficiency of program operation

  1. Deduce the expression type as the definition type of the variable
  2. Deduce the type of the return value of the function

4, final, override

final: modifies the virtual function, indicating that the virtual function can no longer be inherited

override: check whether the virtual function of the derived class overrides a virtual function of the base class. If not, compile and report an error

5, Add container

Newly added containers - static fixed length array, single linked list forward_list and unordered series.

6, Range for loop

Note that when the object stored in the container is relatively large, or the object needs to be copied deeply, such as string, it is best to give & and const to reduce the copy and improve efficiency. The principle of the container supporting the range for: the range for will be replaced by an iterator by the compiler, which means that supporting the iterator supports the range for.

7, Default member function control

Empty classes in C + + will generate some member functions by default, but these functions will not be generated by default if programmers write them themselves. However, sometimes the default generation is required, which is easy to cause confusion. Therefore, C++11 provides two keywords to let the programmer decide whether the compiler generation is required.

default

Adding = default when defining or declaring the default function can indicate that the compiler generates the default version of the function. The function decorated with = default is called displaying the default function

delete

In C++98, if the function is set to private and not defined, others cannot call it.
In C++11, you only need to add = delete to the function declaration. This syntax indicates that the compiler does not generate the default version of the corresponding function, which is often used to prevent copying.

8, Right value reference

The concept of reference is put forward in C++98. Reference is alias. The reference variable shares the same memory space with its reference entity, and the bottom layer of reference is realized through pointer. Therefore, using reference can improve the readability of the program.
In order to improve the running efficiency of the program, the right value reference is introduced in C++11. The right value reference is also an alias, but it can only refer to the right value.

Lvalue: use space
Right value: use content

It is generally believed that:

  1. Variables of ordinary types that can be modified are considered lvalues because they have names and addresses.
  2. Const modified constants are immutable and read-only. In theory, they should be treated as right values, but because they can take addresses (if they are just the definition of const type constants, the compiler will not open up space for them. If they take addresses for the constants, the compiler will open up space for them), C++11 considers them to be left values.
  3. If the result of the expression is a temporary variable or object, it is considered to be an R-value.
  4. If the result of an expression run or a single variable is a reference, it is considered to be an lvalue.
#include<iostream>
using namespace std;
int main()
{
	//lvalue reference 
	int a = 0;
	int& b = a;
	
	//rvalue reference 
	int x = 1, y = 2;
	int&& c = 10;
	int&& d = x + y;
}

Summary:

  1. The left or right value cannot be judged simply by whether it can be placed on the left or right of = or by taking the address. It should be judged according to the result of the expression or the nature of the variable.
  2. The expression that can be referenced must be used as a reference, otherwise it is often referenced.
  3. Lvalue references cannot directly reference right values, but const lvalue references can. Lvalue references cannot reference left values, but the move function can convert lvalues into right values.

C++11 makes a strict distinction between right values (except that right values are left values):
Pure right value (constant of basic type, or temporary object): for example, a+b, 100.
Will be dead value (custom type temporary object): for example, the function returns an object by value.

8.1. Mobile semantics of right value reference

Because the types of lvalue reference and lvalue reference are different, their functions constitute overloads.

C++11 puts forward the concept of mobile semantics, that is, the way to move the resources in one object to another object can effectively alleviate the waste of resources when copying and constructing objects.

class String
{
public:

	String(const char* str = "")
		:_str(new char[strlen(str) + 1])
	{
		strcpy(_str, str);
		cout << "String(const char *str="")" << endl;
	}

	//Normal structure
	String(const String& str)
		:_str(new char[strlen(str._str) + 1])
	{
		//Deep copy is expensive
		strcpy(_str, str._str);
		cout << "Normal structure, deep copy, high cost" << endl;
	}

	//Mobile structure
	String(String&& str)//Is the right value
		:_str(str._str)
	{
		
		str._str = nullptr;//Direct resource transfer, space exchange, low cost and high efficiency
		cout << "Mobile construction, space exchange, low cost and high efficiency" << endl;
	}

	//Move assignment
	String& operator=(String&& str)
	{
		cout << " Mobile assignment, low cost and high efficiency" << endl;
		if (_str != str._str)
		{
			_str = str._str;
			str._str = nullptr;
		}
		return *this;
	}

	//Normal assignment
	String& operator=(const String& str)
	{
		if (_str != str._str)
		{
			cout << "Normal assignment, deep copy, low efficiency" << endl;

			_str = new char[strlen(str._str) + 1];

			strcpy(_str, str._str);
		}

		return *this;
	}

	~String()
	{
		if (_str)
		{
			delete[]_str;
			cout << "~String()" << endl;
		}
	}

private:
	char* _str;
};

String Getstring(const char* str)
{
	String ret(str);
	//This function returns a temporary object, which is an R-value
	return ret;
}

void test()
{
	String s1("Lvalue");
	cout << endl;
	String s2(s1);
	cout << endl;
	String s3(Getstring("Right value-Will die"));
	cout << endl;
	String s4(move(s1));
	cout << endl;
	String s5("Lvalue");
	s5 = Getstring("Right value-Will die");
	cout << endl;


}

int main()
{
	test();

	return 0;
}

Moving construction and moving assignment directly assign the space of the dead value (right value) to the object to be assigned. Because the dead value is destructed when it is out of the scope, it is better to use its space to assign the object to be assigned instead of letting it destruct in vain, so as to avoid the reduction of efficiency caused by deep copying.

In the container of C++11, the insertion of right value moving copy is also added:

8.2. Problems needing attention in mobile semantics

be careful:

  1. The parameters of the mobile constructor must not be set to the R-value reference of const type, because the resources of const cannot be transferred, which will lead to the invalidation of mobile semantics.
  2. In C++11, the compiler will generate a mobile construct for the class by default, which is a shallow copy. Therefore, when resource management is involved in the class, users must explicitly define their own mobile construct.

8.3. The right value refers to the left value

According to the syntax, an R-value reference can only refer to an R-value, but must an R-value reference not refer to an l-value? Because: in some scenarios, you may really need to use the right value to reference the left value to realize the mobile semantics. When you need to reference an lvalue with an R-value reference, you can convert the lvalue to an R-value through the move function. The name of this function is confusing. It does not move anything. Its only function is to forcibly convert an lvalue into an lvalue reference, and then realize the moving semantics.

Usage and precautions of move

int main()
{
 String s1("hello world");
 String s2(move(s1));
 String s3(s2);
 return 0; 
 }
  1. The life cycle of the transformed lvalue does not change with the transformation of lvalue, that is, the lvalue variable transformed by std::move will not be destroyed.
  2. STL also has another move function, which is to move elements in a range to another location.
  3. After move converts s1 into an R-value, the move structure will be used when copying s2. At this time, the resources of s1 will be transferred to s2, and s1 will become an invalid character.
class Person
{
public:

	Person(const String &name)
	:_name(name)//Call the copy constructor of String
	{}

	Person(const Person& pl)
		:_name(pl._name)
	{
		cout << "Person(const Person& pl)" << endl;
	}

private:
	String _name;
};

Optimize the above code and add the right value reference:

class Person
{
public:

	Person(const String &name)
	:_name(name)//Call the copy constructor of String
	{}

	Person(const Person& pl)
		:_name(pl._name)
	{
		cout << "Person(const Person& pl)" << endl;
	}

	Person(const Person&& pl)
		:_name(pl._name)//The string in pl is an lvalue, so it is still a deep copy of the call	
	{
		cout << "Person(const Person&& pl)" << endl;
	}

private:
	String _name;
};

To continue the optimization, use the move function:

class Person
{
public:

	Person(const String &name)
	:_name(name)//Call the copy constructor of String
	{}

	Person(const Person& pl)
		:_name(pl._name)
	{
		cout << "Person(const Person& pl)" << endl;
	}

	Person(Person&& pl)
		:_name(move(pl._name))//Since pl is a dead value, its resources are also a dead value
	{
		cout << "Person(Person&& pl)" << endl;
	}


private:
	String _name;
};

8.4. summary

Reference function in C++98: because reference is an alias, where pointer operation is required, pointer can be used instead, which can improve the readability and security of the code.
The right value reference in C++11 has the following functions:

  1. Implement mobile semantics (mobile construction and mobile assignment)
  2. Alias intermediate temporary variables
int main()
{
 string s1("hello");
 string s2(" world");
 string s3 = s1 + s2; // s3 is a new object constructed from the result copy after the splicing of s1 and s2
 stirng&& s4 = s1 + s2; // s4 is the alias of the result after the splicing of s1 and s2
 return 0; }
  1. Perfect forwarding

8.5. Perfect forwarding

Perfect forwarding means that in the function template, the parameters are passed to the other function called in the function template exactly according to the type of the template parameter.

void Func(int x) {
 // ......
}
template<typename T>
void PerfectForward(T t) {
 Fun(t);
}

PerfectForward is the template function of forwarding and Func is the actual objective function, but the above forwarding is not perfect. Perfect forwarding is that the objective function always wants to transfer the parameters to the objective function according to the actual type passed to the forwarding function without additional overhead, as if the forwarder does not exist.
The so-called perfection: when a function template passes its own formal parameters to other functions, if the corresponding arguments are lvalues, it should be forwarded to lvalues; If the corresponding argument is an R-value, it should be forwarded as an R-value. This is done to preserve that other functions handle the left and right value attributes of the forwarded parameters differently (for example, copy semantics is implemented when the parameter is left value; move semantics is implemented when the parameter is right value).

C++11 realizes perfect forwarding through the forward function, such as:

void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; }
void Fun(const int& x) { cout << "const lvalue ref" << endl; }
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }
template<typename T>
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); }
int main()
{
	PerfectForward(10); // rvalue ref

	int a;
	PerfectForward(a); // lvalue ref
	PerfectForward(std::move(a)); // rvalue ref
	const int b = 8;
	PerfectForward(b); // const lvalue ref
	PerfectForward(std::move(b)); // const rvalue ref
	return 0;
}

If the forward function is not used, the right value becomes the left value.

Reference:
C++11 common syntax - one
Section 3 list initialization - std::initializer_list

Keywords: C++ Back-end

Added by sir nitr0z on Wed, 26 Jan 2022 18:28:26 +0200