More effective C++: Item 3. Never handle arrays polymorphically and Item 4: Do not provide default construcor unless necessary

Item 3. Never handle arrays polymorphically

class BST{
    
    ...};
class BalancedBST:public BST{
    
    ...};

Assume that both BST and BalancedBST contain only ints. Now consider printing the contents of each BST in the BSTs array:

void printBSTArray(ostream &s, const BST array[], int numElements){
    
    
	for(int i = 0; i < numElements; ++i){
    
    
		s << array[i];  //假设BST objects有一个operator<<可用
	}
}

If an array of BST objects is passed to this function, there is no problem. But if an array of BalancedBST objects is given to the printBSTArray function, then there will be a problem.
The specific reasons are as follows: array[i] actually represents *(array+i). Since array is a BST type, when actually calculating the memory address of array[i], array[0 The distance between ] and array[i] is i*sizeof(BST). But since we are passing in BalancedBST, derived classes usually have more data members than base classes, so BalancedBST here is usually larger than BST, so the position offset is also larger than calculated in the original function. Since the location of the memory address offset is incorrect, the results are actually unpredictable.

Similarly, if you delete an array composed of derived class objects through a base class pointer, the result is also undefined. Of course, the ideal situation is to call the destructor of each derived class through the base class pointer, but the actual situation is not. The reason is also because the memory offset is calculated according to the size of the base class, so it is not reasonable. Without finding the starting address of each derived object, the destructor cannot be called correctly.
Summary: Polymorphism and pointer arithmetic cannot be mixed. Array objects almost always involve pointer arithmetic operations, so arrays and polymorphism should not be mixed.

Clause 4: Do not provide default construcor unless necessary

First of all, many commonly used data structures or pointer values ​​can be set to null or 0 or empty. However, in specific life situations, if many classes need to be initialized, we must initialize them reasonably, because it is meaningless to use the default constructor. For example, if a certain instrument is produced, it must have a matching ID number, otherwise it will be meaningless.

class EquipmentPiece{
    
    
public:
	EquipmentPiece(int IDNumber);
	...
};

Then there is no default constructor, which brings problems in the following three aspects.
1. Object arrays cannot be generated in batches. Solution: Generate a pointer array instead of an object array, and then initialize the pointer array to point to different specific objects. It should be noted that you must remember to delete all objects pointed to by the array, otherwise resource leakage will occur and more resources will be occupied. One is to place pointers, and the other is to place specific objects.
The problem of excessive memory usage can be solved by allocating memory first and then using placement new to construct on this memory.
as follows:

//先申请容纳10个EquipmentPiece的内存
void *rawMemory = operator new[](10*sizeof(EquipmentPiece));
//让bestPieces指向这块内存,使这块内存被视为一个EquipmentPiece数组
EquipmentPiece *bestPieces = static_cast<EquipmentPiece*>(rawMemory);
//利用placement new构造这块内存的EquipmentPiece对象
for(int i=0;i<10;++i){
    
    
	new (&bestPiece[i]) EquipmentPiece( ID number);
}

The disadvantage of placement new is that programmers don't know much about it, and they have to manually call destructors, and finally operator delete[] is required to release the initially applied memory.

//将bestPieces中的各个对象,以构造顺序的相反顺序析构掉
for(int i=9;i>=0;--i){
    
    
	bestPieces[i].~EquipmentPiece();
}
//释放raw memory
operator delete[](rawMemory);

2. Not applicable to many template-based container classes
Many template-based container classes may pass in the size of a container, and then the initialization container code inside is used directly. new to open up an object space of this size, in which T::T() is called. Then an error will occur.
3. About virtual base classes
If the virtual base class does not provide a default constructor, then there is only one meaningful constructor, then it is required, no matter how many levels are derived , all require the deepest derived class to understand its meaning and give the correct constructor argument of the base class, because the arguments of the virtual base class constructor are provided by the deepest derived class. This is actually a very tedious matter.

Finally, some people still support that classes should provide a default constructor, even if there is not enough information to completely initialize the object. For example, allowing the above ID to be a magic number means that no ID value is specified. The impact is: most member functions need to check whether the ID exists, and the corresponding test code has a space cost. If the test result is negative, the corresponding handler requires space cost.
So the final conclusion is: If the class constructor can ensure that all fields of the object will be initialized correctly, then all the above costs can be eliminated. If the default constructor cannot provide this guarantee, then it is best to avoid the default constructor.

おすすめ

転載: blog.csdn.net/qq_43847153/article/details/127889498