C + + right value reference

Posted by spillage on Thu, 17 Feb 2022 00:25:56 +0100

Concept of right value reference

Book back: C + + implementation of MyString.

Continue to use the MyString code here

int main()
{
	String s1;
	s1 = fun();

	int a = 10;
	int& b = a;
	//int&& c = a;  Error!!! Only right values can be referenced
	int&& c = 10;//Only r-values can be referenced. Numeric constants are pure r-values
	//int&& d = c;  Error!!! Having a name is no longer an R-value

	String& sx = s1;  //String& sx = String("hello"); Error!!! Lvalue references can only refer to named objects
	String&& sy = String("hello");//Nameless objects can be referenced by right values
	
}

The concept of right value: the left value has an address, and its lifetime is the same as that of the name; The right value cannot take an address, and there is no name

When an R-value has a name, it becomes an l-value concept

String&& fun()
{
	String s2("456");
	return s2;
}

This is an error. s2 has a name and cannot be referenced by an R-value

After the right value reference is clear, you can write the right value copy construction and right value assignment statement, which is also called moving copy construction and moving assignment statement

String(String&& s)
{
	cout<< "move copy construct:" << this << endl;
	str = s.str;
	s.str = NULL;
}
String& operator=(String&& s)
{
	if(this != &s)
	{
		str = s.str;
		s.str = NULL;
	}
	cout<< this << "move operator= "<< &s << endl;
	return *this;
}



String fun()
{
	String s2("456");
	return s2;
}
int main()
{
	String s1;
	s1 = fun();
	cout << s1 << endl;

	return 0;
}

First, build s1 object in main stack frame, and then call fun() function to create s2 object; Here, we need to take s2 to build an unnamed object, mobilize the mobile copy structure, transfer the resources to the dead value object, then call the mobile assignment statement, and transfer the resources to s1. From beginning to end, the resources are the heap resources originally created by s2 object, but only transferred

Why does the right value of the move copy construct refer to a famous object

When the fun() function is returned, s2 is returned as a value to build an unnamed object, that is, the dead value object is constructed by moving the copy. The this pointer in the moving copy structure is to initialize the space of the dead value object through s2

String(String&& s)
{
	cout<< "move copy construct:" << this << endl;
	str = s.str;
	s.str = NULL;
}

But from this point of view, s2 is a famous object. Why can a famous object be initialized through an R-value reference; This belongs to the characteristics of the system (the system forcibly stipulates the transmission s2). The construction will lose value. If there is no mobile structure, the ordinary structure will be transferred. If there is a mobile structure, the mobile structure will be transferred first (c11 standard); We give s2 as a parameter to s, and the dead value object is not s

After the construction of the dead value object is completed, exit from the fun() function, end the s2 lifetime and destruct it. At this time, str in s2 points to null, and there is no space to be released

Return to the main function, assign the dead value object to s1, and transfer the mobile assignment here; The str of s1 points to the space originally belonging to s2, which belongs to the dead value at this time, and changes the dead value STR to a null value

Therefore, when the system constructs the dead value, the s2 named object is referenced to s through the right value; In fact, at the bottom, the following architecture process is carried out

String fun()
{
	String s2("456");
	return std::move(s2);
}

Strongly convert s2 to right value object and assign value; That is, in the return process, you can assign a named object to the mobile structure for binding

Memory leak problem

Here, we finally pass the dead value of the space originally belonging to s2 to s1, but the one byte space originally belonging to s1 is not released, resulting in memory leakage

How to solve the problem of memory leakage:

  1. The first method is to add delete in the move assignment to release the space
String& operator=(String&& s)
{
	if(this != &s)
	{
		delete[]str;
		str = s.str;
		s.str = NULL;
	}
	return *this;
}
  1. Through an exchange function, the space pointed to by two objects is exchanged
String& operator=(String&& s)
{
	if(this != &s)
	{
		s.str = Relese(s.str);//Decomposing the dead value will release the space originally pointed to by s1
	}
	return *this;
}
char* Relese(char *p)
{
	char* old = str;
	str = p;
	return old;
}

Copy on write

According to our original code, when building S1, S2, S3 and S4, each object has its own heap space

int main()
{
	String s1("123");
	String s2(s1);
	String s3(s2);
	String s4(s2);
}

If you point S2, S3 and S4 to the heap space of s1, you will save some space, but you need to add the reference count value to count how many objects in the string are shared

During destruct, if we destruct s4, reduce the reference count by one, continue destruct S3, and continue to reduce S2 by one until the reference count value of destruct s1 is reduced by one to zero, that is, no object points to the space, then the space is released

The structure defined by this type is called flexible array

class String
{
private:
	struct StrNode
	{
		int ref;//Number of object references
		int len;//String length
		int size;//String space size
		char data[];
	};
};

A flexible array is an array whose size is undetermined

In C language, a structure can be used to generate a flexible array, and the last element of the structure can be an array of unknown size
In struct_ sd_ In the node structure, data is only an identifier to be used and does not occupy storage space, so sizeof(struct_sd_data) = 8

  • Purpose: the main purpose of an array with a length of 0 is to meet the structure with variable length
  • Usage: at the end of a structure, declare an array with a length of 0 to make the structure variable; For the compiler, the array with length 0 does not occupy space at this time, because the array name itself does not occupy space, it is only an offset, and the symbol of array name itself represents an immutable address constant; But for this array size, we can allocate it dynamically

Note: if the structure is produced through dynamic allocation methods such as calloc, malloc or realloc, release the corresponding space when it is not needed

The advantage of this method is that it is more efficient to assign variables in a structure than in a pointer

Disadvantages: in the structure, the array with array 0 must be declared at the end, and there are certain restrictions in the design structure type

Practical examples

class String
{
private:
	struct StrNode
	{
		int ref;//Number of object references
		int len;//String length
		int size;//String space size
		char data[];
	};
	
	StrNode *pstr;
public:
	String(const char *p = NULL) :pstr(NULL)
	{
		if(p != NULL)
		{
			int len = strlen(p);
			pstr = (StrNode*)malloc(sizeof(StrNode) + len * 2 + 1);//The length of the flexible array is twice the length of the p storage memory plus one
			pstr->ref = 1;
			pstr->len = len;
			pstr->size = len * 2;
			strcpy(pstr->data, p);
		}
	}
	String(const String& s) :pstr(NULL)
	{
		if(s.pstr != NULL)
		{
			pstr = s.pstr;
			pstr->ref += 1;
		}
	}
};
int main()
{
	String s1("123456");
	String s2(s1);
	String s3(s1);
	String s4(s2);
	
	return 0;
}



Since each object shares a string, when we need to return the space to the heap, we need to reduce the reference count. Only when the reference count is zero, we can return the space

~String()
{
	if(pstr != NULL)
	{
		pstr->ref--;
		if(ref == 0)
		{
			free(pstr);
		}
	}
	pstr = NULL;		
}

Next is the assignment function, output stream overload and subscript overload

class String
{
	String& operator=(const String& s)
	{
		if(this != &s)
		{
			~String();
			new(this) String(s);
		}
	}
	ostream& operator<<(ostream& out) const
	{
		if(pstr != NULL)
		{
			out << pstr->data;
		}
		return out;
	}

	char& operator[](const int index) //Subscript overload
	{
		if(index >= 0)
		{
			for(int i = 0; i < pstr->len; i++)
			{
				pstr->data++;
			}
			return data;
		}

	}
	const char& operator[](const int index) const
	{
		if(index >= 0)
		{
			for(int i = 0; i < pstr->len; i++)
			{
				pstr->data++;
			}
			return data;
		}
	}
}
ostream& operator<<(ostream& out, const String& s)
{
	s << out;
	return out;
}
int main()
{
	String s1("123");
	char x = s1[2];
	s1[2] = 'x';
}

Topics: C++ Back-end