Original:C + + "class" Considerations Summary (12): Initialize by member and assign value by member
first, initialize by member (related to constructors and copy constructors )
Initializes another class object with one class object, such as:
Account Oldacct ("Anna Livia Plurabelle");
Account NEWACCT (OLDACCT);
is called the default by member initialization (default memberwise initialization), by default because it occurs automatically, regardless of whether we provide an explicit constructor, by member because the initialized cell is a single non-static data member, rather than a bitwise copy of the entire class object.
For example, the first definition of an account class:
Class Account {
Public
// ...
Private
Char *_name;
unsigned int _acct_nmbr;
Double _balance;
};
We can assume that the default account copy constructor is defined as follows:
Inline account::
Account (const account &RHS)
{
_name = Rhs._name;
_ACCT_NMBR = RHS._ACCT_NMBR;
_balance = rhs._balance;
}
initializing the class with a class object another object occurs in the following program scenario:
1 explicitly initializes another class object with one class object, for example:
Account NEWACCT (OLDACCT);
2 Pass a Class object as an argument to a function, for example:
extern bool Cash_on_hand (account acct);
if (Cash_on_hand (OLDACCT))
// ...
The return value of a class object as a function is passed back, for example:
extern account
Consolidate_accts (const vector< account >&)
{
Account Final_acct;
Do the finances ...
return FINAL_ACCT;
}
3 definition of a non-empty sequential container type, for example:
Five string copy constructors are called
Vector < string > Svec (5);
(In this example, you create a temporary object with the string default constructor, and then copy the constructor through a string, which is copied sequentially into the five elements of the vector.) )
4 Insert a Class object into a container type, for example:
Svec.push_back (String ("Pooh"));
For most of the actual class definitions, due to the security of the class and the correctness of usage, it is not enough to say that the default per-member initialization is sufficient, and the most common scenario is that the data member of a class is a pointer to the heap memory, and this block of memory is deleted by the destructor of the class. Just like the _name member in the account class.
After the default by member initialization, Newacct._name and Oldacct._name point to the same C-style string, and if Oldacct leaves the domain and the account's destructor is applied to it, Newacct._name now points to a deleted Memory area, the other is that OLDACCT is also affected if NEWACCT modifies a string pointed to by _name, which is difficult to track.
One solution to the pointer alias (aliasing) problem is to assign the second copy of the string and initialize the Newacct._name to point to the new copy, and for this to happen, we must change the default by member initialization of the account class, We do this by providing an explicit copy constructor.
The internal semantics of the class may also invalidate the default by-member initialization, as explained earlier, cannot have two account class objects holding the same accounts, in order to guarantee this, we have to change the default by member initialization, the following is the copy constructor to solve these two problems:
Inline account::
Account (const account &RHS)
{
Handling pointer aliasing issues
_name = new char[strlen (rhs._name) +1];
strcpy (_name, rhs._name);
Dealing with account uniqueness issues
_ACCT_NMBR = GET_UNIQUE_ACCT_NMBR ();
OK: You can now copy by member
_balance = rhs._balance;
}
In addition to providing a copy constructor, another alternative is to not allow by member initialization at all, which can be achieved in the following two steps:
1 The copy constructor is declared private , which prevents initialization by member from occurring anywhere in the program (except for the class's member functions and friends).
2 by intentionally not providing a definition , however, we still need the declaration in step 1th to prevent the member functions and friends in the class from appearing by member initialization. The C + + language does not allow us to block member functions and friends of a class from accessing any private class member, but by not providing a definition, any action that attempts to invoke the copy constructor is legitimate in the compilation system but generates a link error because a resolvable definition cannot be found for it.
For example, in order not to allow the account class to be initialized by members we must declare the class as follows:
Class Account {
Public
Account ();
Account (const char*, double=0.0);
// ...
Private
Account (const account&);
// ...
};
Ii. initialization of Member class objects
What happens if you replace the _name declaration of a C-style string with the _name declaration of a string class type?
The default per-member initialization examines each member in turn, and if the member is a built-in or composite data type, the initialization from member to member is performed directly. For example, in our original account class definition, because _name is a pointer, it is initialized directly:
Newacct._name = Oldacct._name;
However, the processing of the Member class objects is different when we write the following statement:
Account NEWACCT (OLDACCT);
these two objects are recognized as account class objects, and if the account class provides an explicit copy constructor, it is called to complete initialization, otherwise the default by member initialization is applied, or, similarly, when a member class object is recognized, the same procedure is applied recursively.
In our example, the string class provides an explicit copy constructor that _name is initialized by invoking the copy constructor. Now we can assume that the default account copy constructor is defined as follows:
Inline account::
Account (const account &RHS)
{
_ACCT_NMBR = RHS._ACCT_NMBR;
_balance = rhs._balance;
C + + pseudo code
The description invokes a class member
Copy constructors for objects
_name.string::string (Rhs._name);
}
The default per-member initialization process for the account class now correctly handles _name allocation and deallocation, but the copy accounts are still not correct, so we still have to provide an explicit copy constructor, and the following code is not quite correct. Can you see why?
Not very right.
Inline account::
Account (const account &RHS)
{
_name = Rhs._name;
_balance = rhs._balance;
_ACCT_NMBR = GET_UNIQUE_ACCT_NMBR ();
}
The implementation is not entirely correct because we do not have a partition to initialize and assign the result, the call is not a string copy constructor, but in the implicit initialization phase called the default string constructor, and in the constructor body called the string copy assignment operator . The fix is simple:
Inline account::
Account (const account &RHS)
: _name (Rhs._name)
{
_balance = rhs._balance;
_ACCT_NMBR = GET_UNIQUE_ACCT_NMBR ();
}
Once again, the real job is to realize at the outset that we need to provide a fix for both implementations that _name holds the value of rhs._name, except that the first implementation requires two repetitions, and a general rule is to initialize all Member class objects in the member initialization table. .
Assign values by member (related to the copy assignment operator )
The default assignment by member (default Memberwise Assignment) is to manipulate the assignment of one class object to another object of the class, essentially the same as the default by member initialization, but it takes advantage of an implicit copy assignment operator To replace the copy constructor , for example:
Newacct = Oldacct;
By default, the values of the corresponding members of OLDACCT are assigned to each non-static member of NEWACCT in turn, conceptually as if the compiler had generated the following copy assignment operators:
Inline account&
Account::
operator= (const account &RHS)
{
_name = Rhs._name;
_balance = rhs._balance;
_ACCT_NMBR = RHS._ACCT_NMBR;
}
In general, if the default per-member initialization is inappropriate for a class, the default value-by-member assignment is not appropriate. For example, for the definition of the original account class, where _name is declared as char* type _name and _ACCT_NMBR are assigned by member, it is inappropriate.
By providing an instance of an explicit copy assignment operator, you can change the default by-member assignment, and we implement the correct class copy semantics in this operator instance, the general form of the copy assignment operator is as follows:
The general form of the copy assignment operator
classname&
ClassName::
operator= (const className &RHS)
{
Guaranteed not to copy itself.
if (This! = &RHS)
{
The class copy semantics are here
}
Returns the object being assigned a value
return *this;
}
Here the condition tests are:
if (This! = &RHS)
you should prevent a class object from assigning itself a value, because it is particularly inappropriate to copy the copy assignment operator, which releases the resource currently associated with the object so that it allocates resources related to the copied object. For example, consider the account copy assignment operator:
account&
Account::
operator= (const account &RHS)
{
Avoid assigning values to yourself
if (This! = &RHS)
{
delete [] _name;
_name = new Char[strlen (rhs._name) +1];
strcpy (_name,rhs._name);
_balance = rhs._balance;
_ACCT_NMBR = RHS._ACCT_NMBR;
}
return *this;
}
When a class object is assigned to another object of the class, such as
Newacct = Oldacct;
Here are a few steps to take place:
1 Check the class to determine if it provides an explicit copy assignment operator;
2 If yes, check the access permission to determine whether it can be called in this program section;
3 If it cannot be called, a compile-time error is generated, otherwise, it is called to perform the assignment operation;
4 If the class does not provide an explicit copy assignment operator, execution defaults by member assignment;
5 The data members of each built-in type or composite type are assigned to the corresponding member by default by member assignment;
6 for each class member object, recursively executes 1 to 6 steps until all data members of the built-in or composite type are assigned values.
For example, if we modify the definition of the account class again so that _name is a member class object of type String , then:
Newacct = Oldacct;
The default value-by-member assignment is called, as if the compiler generated the following copy assignment operator for us:
Inline account&
Account::
operator= (const account &RHS)
{
_balance = rhs._balance;
_ACCT_NMBR = RHS._ACCT_NMBR;
Even at the programmer's level,
This call is also correct.
Equivalent to a short form: _name = Rhs._name
_name.string::operator= (Rhs._name);
}
However, the default value of the account class object is still inappropriate by member assignment, and as the _ACCT_NMBR member is also copied by member , we must still provide an explicit copy assignment operator, but it handles name in the same way as the member class string object:
account&
Account::
operator= (const account &RHS)
{
Prevent class objects from assigning values to themselves
if (This! = &RHS)
{
Call string::operator= (const string&)
_name = Rhs._name;
_balance = rhs._balance;
}
return *this;
}
If you want to completely suppress the behavior of copy-by-member, you need to declare the operator as private and do not provide the actual definition, as you would prohibit by member initialization.
In general, copy constructors and copy assignment operators should be treated as an individual unit, because when we need one, we often need a different one, and when trying to prohibit one, it is also possible to prohibit the other.
"Reprint" C + + and "class" Considerations Summary (12): Initialize by member and assign value by member