C ++ programming specification Class 4

Source: Internet
Author: User

Category 4

Design of the 4.1 category

Class is the basis of object-oriented design. A good class should have a single responsibility, clear interfaces, few but complete interfaces, low coupling between classes, and high internal class

And demonstrate encapsulation, inheritance, polymorphism, modularity, and other features.

Principle 4.1 class responsibilities

Note: The class should have a single responsibility. If a class has too many responsibilities, it is often difficult to design, implement, use, and maintain it.

With the expansion of functions, the category's responsibility scope naturally expands, but its responsibilities should not be divergent.

Use small classes instead of giant classes. Small classes are easier to write, test, use, and maintain. The concept of simple design is embodied in small classes; the huge classes will weaken the seal.

Installation, giants often assume too many responsibilities and try to provide "complete" solutions, but it is often difficult to truly succeed.

If a class has more than 10 data members, the class may have too many responsibilities.

Principle 4.2 hide information

Note: encapsulation is one of the core concepts of object-oriented design and programming. Hide internal data to reduce caller code and actual

Dependencies between modern codes.

Minimize global and shared data;

Forbid member functions to return writable references or pointers to members;
Set Data members as private (except struct) and provide relevant access functions;

Avoid providing access functions for each type of data member;

The Runtime polymorphism separates the internal implementation (provided by the derived class) from the external interface (provided by the base class.

Principle 4.3 try to make the class interface orthogonal, less complete

Note: interfaces, services, and other types of cooperation should be defined around one core to facilitate implementation, understanding, use, and testing.

And maintenance. The interface functions are orthogonal. Avoid overwriting the functions of one interface. Too many interface functions will be difficult to handle

Solution, use, and maintenance. If a class contains more than 20 non-private member functions, the class interface may not be simplified.

Rule 4.1 do not expose private and protected members to external interface classes between modules

Note: The exposure of the external interface class is protected or the encapsulation is damaged by private members. Once the class design changes (add, delete, or modify ),

Internal member) may cause re-Compilation of associated components or system code, thereby increasing the system Compilation Time and binary compatibility issues,

Cause associated upgrades and patching. Therefore, unless necessary, do not expose private and protected members in the interface class.

There are several methods:

The pure virtual class is used as the interface class and the implementation class is used for implementation. The user only sees the interface class. The disadvantage of this method is:

The code structure is relatively complex.

The new interface must be placed behind the original interface, and the order of the original interface cannot be changed. Otherwise, because of the virtual function table,

This will cause the customer code to be re-compiled.

The interface class uses the pimpl mode (only one private data member pointing to the implementation class pointer). All private members are encapsulated in the implementation class.

(The implementation class can be directly placed in the implementation file without being exposed as the header file ).

The code structure is simple and easy to understand.

This can save the virtual function overhead, but has indirect access overhead.

Modifying the implementation does not cause the customer code to be re-compiled.

Class Interface
{
Public:
Void function ();
PRIVATE:
Implementation * impl _;
};

Class implementation
{
Public:
Int I;
Int J;
};

Void interface: function ()
{
++ Impl _-> I;
}

Rule 4.2 prevents member functions from returning writable references or pointers to members

Note: Class encapsulation is damaged. If the object itself is unknown, its members are modified.
Example: bad examples

Class alarm
{
Public:
String & getname () {return name;} // destroys the encapsulation of the class. The member name is exposed.
PRIVATE:
String name;
};
Exception: in some cases, it is necessary to return writable references or pointers, for example, a single-piece mode statement:

Type & type: instance ()
{
Static type instance;
Return instance;
}

Rule 4.3 circular dependency between prohibited classes

Note: Circular dependency will greatly increase the Coupling Degree of the system, so circular dependency is prohibited between classes. Class A depends on Class B, and Class B depends on Class.

In this case, you need to adjust the class design and introduce Class C:

Upgrade: Introduce associated services to Class C so that class C depends on Class A and Class B to eliminate circular dependency.

Downgrade: the associated services are referred to Class C, so that Class A and Class B are dependent on Class C to eliminate circular dependency.

Example: Class rectangle and class window are mutually dependent

Class rectangle
{
Public:
Rectangle (INT X1, int Y1, int X2, int Y2 );
Rectangle (const window & W );
};

Class window
{
Public:
Window (INT xcenter, int ycenter, int width, int height );
Window (const rectangle & R );
};
You can add the boxutil class for conversion without generating mutual dependencies.

Class boxutil
{
Public:
Static rectangle torectangle (const window & W );
Static window towindow (const rectangle & R );
};

We recommend that you set the data member to private (except struct) and provide relevant access functions.

Note: Information Hiding is the key to a sound design. All data members should be set to private, precisely controlling the reading and writing of member variables.

External shielding internal implementation. Otherwise, some states of the class may be uncontrollable and unpredictable because:

Non-Private Members damage the encapsulation of classes, and the class itself does not know when its data members will be modified;

Any modifications to the class will extend to the code that uses the class.

Privatize data members and provide related access functions, such as defining variables Foo _ and value functions Foo () and value assignment operators.

Set_foo (). The access function is generally defined as an inline function in the header file. Private Data members can
No access functions are provided to hide and protect them. Do not access the addresses of private data members by using access functions (see rules

4.2 ).

We recommend that you use pimpl mode in 4.2 to ensure that private members are truly invisible.

Note: C ++ specifies private member members as inaccessible but visible. You can use pimpl to make private members in the current

Class range is invisible. Pimpl achieves the effect of separation between interfaces and implementations through pre-declaration, reducing Compilation Time and

Coupling.

Example:

Class Map
{
PRIVATE:
Struct impl;
Shared_ptr <impl> pimpl _;
};

4.2 structure, value assignment, and Structure Analysis

Rule 4.4 is a class that contains member variables. You must define constructors or Default constructors.

Note: If a class has a member variable, no constructor is defined, and no default constructor is defined, the compiler automatically generates a constructor.

But the constructor generated by the compiler does not initialize the member variables, and the object state is in an uncertainty.

Exception: If this class is inherited from another class and no member variables are added, no default constructor is provided.

Example: The following Code does not have a constructor and private data members cannot be initialized:

Class cmessage
{
Public:
Void processoutmsg ()
{
//...
}
PRIVATE:
Unsigned int msgid;
Unsigned int msglen;
Unsigned char * msgbuffer;
};
Cmessage MSG; // The MSG member variable is not initialized.
MSG. processoutmsg (); // potential risks in subsequent use

// Therefore, it is necessary to define the default constructor as follows:
Class cmessage
{
Public:
Cmessage ():
Msgid (0 ),
Msglen (0 ),
Msgbuffer (null)
{
}
//...
};

Rule 4.5: To avoid implicit conversions, declare a single parameter constructor as explicit
Note: A single-parameter constructor is an implicit conversion function if explicit statements are not used.

Example:

Class foo
{
Public:
Explicit Foo (const string & name): m_name (name)
{
}
PRIVATE:
String m_name;
};

Processfoo ("zhangsan"); // when calling a function, the compiler reports an error because implicit conversion is prohibited explicitly.
Foo: Foo (string & name) is defined. When the real parameter of the foo object is a string, the constructor FOO: Foo (string & name)

It is called and converted into a foo temporary object to be passed to the call function, which may lead to unexpected implicit conversion. Solution:

Add explicit before the constructor to limit implicit conversion.

Rule 4.6 The class containing resource management should be customized to copy constructors, value assignment operators, and destructor

NOTE: If not defined, the compiler will generate a copy constructor, a value assignment operator, and a destructor by default. Automatically Generated copy

Constructors and assignment operators simply assign the members of all source objects to the target objects, that is, shallow copy.

The dynamic generation destructor is empty. This is not enough for classes that contain resource management: for example, for resources requested from the heap

If the source object and target object are directed to the same memory, resources are released repeatedly. Empty destructor will not release in Application

Save.

If you do not need to copy constructors and value assignment operators, you can declare them as private attributes to invalidate them.

Example: If the structure or object contains a pointer, define your own copy constructor and value assignment operator to avoid a wild pointer.

Class gidarr
{
Public:
Gidarr ()
{
Inum = 0;
Pgid = NULL;
}
~ Gidarr ()
{
If (pgid)
{
Delete [] pgid;
}
}
PRIVATE:
Int inum;
Char * pgid;
Gidarr (const gidarr & RHs );
Gidarr & operator = (const gidarr & RHs );
} Gidarr;

Rule 4.7 asks operator = to return * This reference

Note: it meets the common usage and habits of continuous assignment.

Example:
String & string: Operator = (const string & RHs)
{
//...
Return * This; // return the object on the left
}
String w, x, y, z;
W = x = y = z = "hello ";

Rule 4.8 checks the assignment in operator =.

Note: assigning a value to yourself is different from assigning a value to an ordinary one. If you do not prevent problems.

Example:

Class string
{
Public:
String (const char * value );
~ String ();
String & operator = (const string & RHs );
PRIVATE:
Char * data;
};

// Self-assigned value, valid
String;
A =;
// Bad example: Ignore the assignment to yourself, resulting in access to the wild pointer
String & string: Operator = (const string & RHs)
{
Delete [] data; // delete data

// Allocate new memory and copy the RHS value to it
Data = new char [strlen (RHS. Data) + 1]; // RHS. Data has been deleted and becomes a wild pointer.
Strcpy (data, RHS. data );

Return * this;
}

// Good example: Check your assignment
String & string: Operator = (const string & RHs)
{
If (this! = & RHs)
{
Delete [] data;
Data = new char [strlen (RHS. Data) + 1];
Strcpy (data, RHS. data );
}
Return * this;
}

Rule 4.9 assign values to all data members in copy constructors and assignment operators

Note: ensure the Object Integrity of constructors and value assignment operators to avoid incomplete initialization.

Rule 4.10: When a base class pointer is used to perform the delete operation, the destructor of the base class is set to public and virtual

Note: Only when the basic destructor is virtual can the destructor of the derived class be called.
Example: Memory leakage caused by no virtual destructor in the base class definition.

// The following platform defines the base class A to obtain the version number.
Class
{
Public:
Virtual STD: String getversion () = 0;
};
// Product derived class B to implement its specific functions. Its definition is as follows:
Class B: public
{
Public:
B ()
{
Cout <"B ()" <Endl;
M_int = new int [100];
}

~ B ()
{
Cout <"~ B () "<Endl;
Delete [] m_int;
}
STD: String getversion () {return STD: string ("Hello! ");}

PRIVATE:
Int * m_int;
};

// The code used to simulate this interface is as follows:
Int main (INT argc, char * ARGs [])
{
A * P = new B ();
Delete P;
Return 0;
}
Although the derived class B clears Resources in the destructor, unfortunately, the destructor of the derived class will never be called. Because the base class

A does not define destructor, nor does it define virtual destructor. when an object is destroyed, it only calls the default destructor of the system.

Memory leakage.

Rule 4.11 avoid calling virtual functions in constructor and destructor

Note: Calling virtual functions in constructor and destructor will lead to undefined behaviors.

In C ++, a base class constructs only one complete object at a time.

Example: Class basea is a base class, And deriveb is a derived class

Class basea // basea of the base class
{
Public:
Basea ();
Virtual void log () const = 0; // different Derived classes call different log files
};

Basea: basea () // base class Constructor
{
Log (); // call the virtual function log
}
Class deriveb: Public basea // derived class
{
Public:
Virtual void log () const;
};

Run the following statement:

Deriveb B;

The deriveb constructor is executed first, but the basea constructor is called first. Because the basea constructor calls the virtual function log,

At this time, log is the base class version. Only after the base class is constructed can the structure of the derived class be completed, leading to undefined behavior.

The same principle applies to destructor.

We recommend that you copy the constructor and value assignment operator parameters to define the const reference type.

Note: copying constructors and value assignment operators cannot change the objects referenced by them.

It is recommended that 4.4 release resources in the destructor

Note: You can use destructor to centrally clean up resources. If the resource is released (such as the release function) before the destructor ),

Set the resource to null to ensure that the Destructor will not be released again.

4.3 inheritance

Inheritance is a basic feature of object-oriented languages. Understand the meaning of various inheritance: "Public inheritance" means "Yes...", pure virtual

A function only inherits interfaces. Generally, virtual functions inherit interfaces and provide default implementations. Non-virtual functions inherit interfaces and implementations but cannot be modified.

Too many inheritance layers lead to difficulties in understanding. Multi-inheritance significantly increases the complexity of the code and brings potential confusion.

Principle 4.4 replace inheritance with combinations

Note: inheritance and combination can reuse and expand existing capabilities. If a combination can represent the relationship between classes, the combination is preferred.

Inheritance implementation is simple and intuitive, but inheritance is defined during compilation and cannot be changed at runtime. Inheritance exposes the implementation of the base class for the derived class.

Details make the derived class highly coupled with the base class. Once the base class changes, the derived class changes, and the derived class cannot be repaired.

Changing the non-virtual function of the base class affects each derived class.

The combination is more flexible and the code coupling is small, so the combination is preferred.

However, it is not absolute. Combination and inheritance are often used together. For example, the elements of a combination are abstract, and the combination is modified by implementing abstraction.

.

In general, there are two types of inheritance: Implementation inheritance and interface inheritance.

Inheritance), do not consider using a combination instead of implementing inheritance.

Interface inheritance: inherits only the interfaces (that is, declarations) of member functions, such as pure virtual functions. Implement inheritance:

Interfaces and implementations of member functions, such as virtual functions that inherit both interfaces and default implementations, can overwrite the implementations they inherit.

The virtual function inherits the interface and is required to inherit the implementation.

Example: A combination is a member variable of another type embedded, that is, "one" or "implemented ". For example:

Class address {...}; // place where someone lives
Class phonenumber {...}; // someone's phone number
Class person
{
PRIVATE:
String name; // composite member variable
Address; // same as above
Phonenumber voicenumber; // same as above
Phonenumber faxnumber; // same as above
};

Principle 4.5 avoid using multiple inheritance

Note: Compared with single inheritance, multi-implementation inheritance can reuse more Code. However, multi-inheritance significantly increases the complexity of code and the program can be maintained.

Poor protection, and prone to errors during parent class conversion, so do not use multi-implementation inheritance unless necessary, instead of combination.

In multi-inheritance, classes are pure interface classes, and only one class contains implementations.

Rule 4.12 uses public inheritance instead of protected/private inheritance

Note: differences between public inheritance and private inheritance:

Private inheritance represents the relationship "implemented. The compiler does not convert private-inherited Derived classes into base classes, that is

The base class of private inheritance has no "Yes..." relationship with the derived class.

Public inheritance represents the relationship between "Yes...", that is, Class B public inherits from Class A, then the object of B is the object of a, and vice versa.

Otherwise. For example, "a white horse is a horse, but a white horse is not a horse ".

In terms of inheritance, we strive to achieve a "Yes..." relationship, otherwise we will use a combination instead.

Private inheritance means "implemented by...", which is generally lower than the combination level, and different from the combination:

Private inherits the protected members of the base class, but the combination cannot.

Private inheritance can redefine the virtual functions of the base class, but cannot combine.

Try to replace private inheritance with combination, because private inheritance is not as simple and intuitive as combination, and is easy to be confused with public inheritance.

Rule 4.13 has no more than four inheritance Layers

Note: When there are more than four layers of inheritance, the maintainability of the software is greatly reduced. You can try to replace inheritance with a combination.

Rule 4.14 virtual functions do not use default parameter values

Note: In C ++, virtual functions are dynamically bound, but the default parameters of the functions are statically bound during compilation. This means that you are the most

The final function is a virtual function defined in the derived class, but uses the default parameter values in the base class. Therefore, as long as it is defined in the base class

The default parameter value is enough. do not define the default parameter value in the derived class.

Example: The default value of the display parameter of the virtual function, strshow, is determined by the compile time, rather than the run time. It does not achieve the purpose of polymorphism:

Class base
{
Public:
Virtual void display (const STD: string & strshow = "I am base class! ")
{
STD: cout <strshow <STD: Endl;
}
Virtual ~ Base (){}
};
Class derive: public Base
{
Public:
Virtual void display (const STD: string & strshow = "I am derive class! ")
{
STD: cout <strshow <STD: Endl;
}
Virtual ~ Derive (){}
};
Int main ()
{
Base * pbase = new derive ();
Derive * pderive = new derive ();
Pbase-> display (); // program output result: I am base class! Expected output: I am deriveclass!
Pderive-> display (); // program output result: I am derive class!
Delete pbase;
Delete pderive;
Return 0;
};

Rule 4.15 will never redefine the inherited non-virtual functions

Note: because non-virtual functions cannot be dynamically bound, only virtual functions can be dynamically bound. You only need to operate the pointer of the base class.

Obtain the correct result.

For example, Pb-> MF () and Pd-> MF () have different behaviors.

Class B
{
Public:
Void MF ();
//...
};

Class D: Public B
{
Public:
Void MF ();
//...
};

D x; // X is an object of Type D
B * pb = & X; // get pointer to X
D * Pd = & X; // get pointer to X

Pb-> MF (); // callb: MF
Pd-> MF (); // calld: MF

4.5 avoid defining functions with the same name as the base class but with different parameter types in the derived class.

Note: functions of different parameter types are actually different functions.

Example: There are three classes. The Inheritance relationships between classes are as follows: Class derive2 inherits class derive1 and class derive1 inherits class base.

The Foo function is implemented in all the three classes and is defined as follows:

Base Class: Virtual long Foo (const A, const B, const c) = 0;

Derive1 class: Long Foo (const A, const B, const C );

Derive2 class: Long Foo (const a, B, const C );

The following calls exist in the Code:

Base * baseptr = new derive2 ();

Baseptr-> Foo (A, B, C );
The Code is intended to call the derive2: Foo function through the above Code. However, due to derive2: Foo and base: Foo

The parameter types are inconsistent. derive2: foo is invisible to the base class, causing calling

Derive1: Foo. A call error occurs. Causes code logic exceptions.

Solution: Make sure that the derive2: Foo definition of the derived class is consistent with that of base: Foo.

We recommend that you declare the virtual keyword for the redefined virtual function of the 4.6 derived class.

Note: When you redefine a derived virtual function, it is explicitly declared as virtual in the derived class. If the virtual statement is missing, read the reader

You need to retrieve all the ancestors of the class to determine whether the function is a virtual function.

4.4 heavy load

The overload function of C ++ enables multiple implementation methods for functions with the same name to simplify interface design and use. However, it is necessary to make proper use

And potential problems. Keep the natural semantics of overloaded operators and do not blindly innovate.

Principle 4.6 Do not overload operators as much as possible to maintain the natural semantics of heavy-duty operators

Note: Heavy-duty operators must have good reasons and do not change the original semantics of operators. For example, do not use the '+' operator for subtraction.

Operator Overloading makes the code more intuitive, but there are also some shortcomings:

Obfuscation intuition, mistakenly believing that this operation is as high as the built-in type, ignoring the possibility of performance reduction;

It is not intuitive to locate the problem, and searching by function name is more convenient than searching by operator.

If the behavior definition of the overload operator is not intuitive (for example, the '+' operator is used for subtraction), the code will be confused.

Implicit conversions introduced by the overload of the value assignment operator can hide deep bugs. You can define functions such as equals () and copyfrom ().

Replace =, = Operator.

Rule 4.16 only reloads functions with different input parameter types and different functions

Note: using overload makes it difficult to determine which function is called in a specific call. When a derived class only reloads the function

The semantic meaning of inheritance is confusing, resulting in unnecessary puzzles.

If the functions of a function are different, let the function name contain parameter information, for example, use appendname (), appendid () instead

Append ().

We recommend that you use overload 4.7 to avoid implicit type conversion.

Note: Implicit conversions often create temporary variables. If you provide an overloaded function with exact type matching, conversion will not occur.

Example:

Class string
{
//...
String (const char * text); // implicit conversion is allowed.
};
Bool operator = (const string &, const string &);
//... Somewhere in the code...
If (somestring = "hello "){...}
In the above example, the compiler performs implicit conversion, as if somestring = string ("hello"), which is a waste because it does not need

To copy characters. This implicit conversion can be eliminated by using the operator overload:
Bool operator = (const string & LHS, const string & RHs); // #1
Bool operator = (const string & LHS, const char * RHs); // #2
Bool operator = (const char * LHS, const string & RHs); // #3

We recommend that you use 4.8 C/C ++ to avoid overloading interface functions.

Note: at present, many products adopt a mix of C and C ++ modules. In this case, interface functions between modules should be avoided.

Overload. For example, the function pointer passed to the component implemented in C language.

Stpdiamlicallbackfun. pfcreateconn = pdiam_com_creatconnect;
Stpdiamlicallbackfun. pfdeleteconn = pdiam_com_deleteconnect;
Stpdiamlicallbackfun. pfsendmsg = pdiam_com_senddata;
Stpdiamlicallbackfun. pfrematchconn = pdiam_com_rematchconn;
Stpdiamlicallbackfun. pfsucloseacceptsocket = pdiam_com_closeacceptsocket;

// Register the underlying communication function of the system
Ulret = diamreglifunc (& stpdiamlicallbackfun );
If (diam_ OK! = Ulret)
{
Return pdiam_err_failed_stack_init;
}
In the code above, because the component is implemented in C language, if you overload functions such as pdiam_com_creatconnect, this component will

Initialization fails.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.