Objective C ++ Reading Notes (12)

Source: Internet
Author: User

Clause 19: design class is like design type

Treat class design as type design

In C ++, just like other object-oriented programming languages, a new type can be defined by defining a new class. As a C ++ developer, you spend a lot of time expanding your type system. This means that you are not only a class designer, but also a type designer. Overload functions and operators, Control Memory Allocation and collection, and define object initialization and termination processes-all of which are under your control. Therefore, you should put a lot of effort into the class design, just as the great effort of the language designer in the language built-in type design.

Well-designed classes are challenging because well-designed types are challenging. Good types have simple and natural syntaxes, intuitive semantics, and one or more efficient implementations. So how can we design efficient classes? First, you must understand your problems. In fact, you need to deal with the following questions for each class. The answer is usually directed to your design specifications:

· How should I create and destroy New types of objects? How to do this will affect the constructor and destructor of your class, as well as the memory allocation and recovery functions (operator new, operator new [], operator delete, and operator delete []). unless you do not write them.

· What are the differences between object initialization and object assignment? The answer to this question determines the behavior of your constructor and the assignment operator and their differences.

· What does passed by value mean for new types of objects? The copy constructor defines how to implement a new type of data transfer.

· What is the valid value of the new type? Generally, a combination of only some values is valid for a data member of a class. Those numeric sets determine the constraints that your class must maintain. It also determines the error checks that must be performed inside the member functions, especially constructors, value assignment operators, and "setter" functions. It may also affect the exceptions thrown by the function and (rarely used) the detail of function exceptions (exceptionspecification ).

· Do your new type need to work with an inherited graph? If you inherit from an existing class, you will be subject to the design constraints of those classes, especially the impact of whether their functions are virtual or non-virtual. If you want to allow other classes to inherit from your class, it will affect whether you declare the function as virtual, especially your destructor.

· Which type of conversion is allowed for your new type? Your type is in another type of ocean, so do you want to have some conversions between your type and other types? If you want to allow objects of the T1 type to be implicitly converted to objects of the T2 type, you can either write a type conversion function (such as operator T2) in the T1 class ), either write a non-explicit-one argument constructor In the T2 class. If you only allow the display of constructor, you must write a function specifically responsible for executing the conversion, and not be a type conversion operator or non-explicit-oneargument constructor.

· Which operators and functions are reasonable for the new types? The answer to this question determines which functions you declare for your class. Some of them are member functions, while others are not.

· Which standard functions should be rejected? You need to declare all of them as private.

· Which members of your new type can be accessed? This problem can help you decide which members are public, which are protected, and which are private. It can also help you decide which classes and/or functions should be friends, and whether a class is nested in another class makes sense.

· What is the new type of undeclared interface "undeclaredinterface "? Which of the following guarantees is provided for efficiency, exceptional security, and resource usage (such as multi-task locking and dynamic memory? Your guarantee in these fields will add corresponding constraints to your class implementation code.

· How universal is your new type? Maybe you don't really want to define a new type, maybe you want to define a whole type family. If so, you should not define a new class, but define a new class template.

· Is a new type really what you need? Can you just define a new inherited class so that you can add some functions to an existing class, maybe you can achieve your goal by simply defining one or more non-member functions or templates.

· Class design is the type design. Defining efficient classes is challenging. In C ++, the type generated by the user-defined class should be the same as the built-in type.

 

Clause 20: replace pass-by-reference-to-const with pass-by-value.

Prefer pass-by-reference-to-const to pass-by-value

By default, C ++ passes an object into or out of a function by passing values (this is a feature inherited from C ). Unless otherwise specified, the function parameter will be initialized with a copy of the actual parameter, and the function caller will receive a replica of the function return value. This replica is generated by the object copy constructor, which makes it a costly operation to pass the value. For example, consider the following class inheritance system:

Class Person {
Public:
Person (); // The parameter is omitted for simplicity.
Virtual ~ Person ();
...

Private:
Std: string name;
Std: string address;
};

Class Student: public Person {
Public:
Student (); // omit the parameter again
~ Student ();
...

Private:
Std: string schoolName;
Std: string schoolAddress;
};

Now, consider the following code. Here we call the validateStudent function to get a real Student parameter (in the value passing method) and return whether it is valid:

Bool validateStudent s; // The function accepts Student by value.

Student plato;

Bool platoIsOK = validateStudent (plato); // call the function

Obviously, Student's copy constructor is called and plato is used to initialize the parameter s. Similarly, when validateStudent returns, s will be destroyed. Therefore, the parameter transfer cost of this function is the call of a copy constructor of Student and the call of a destructor of Student.

But this is not all. The Student object contains two string objects. The Student object also inherits from a Person object, and the Person object contains two additional string objects. In the end, the consequence of passing a Student object by passing the value is to call the Student copy constructor and the Person copy constructor, and four string copy constructor calls. When a copy of the Student object is destroyed, each constructor call corresponds to a constructor call, therefore, the full cost of passing a Student by passing values is six constructors and six destructor!

This is correct and worthwhile. After all, you want all objects to be reliably initialized and destroyed. However, the pass by reference-to-const method will be better:

Bool validateStudent (const Student & s );

This is very effective: No constructor or destructor are called because no new object is constructed. After modification, the const In the parameter Declaration is very important. Originally, validateStudent accepted a Student parameter in the by-value method, so the caller knows that the function will never make any changes to the Student they pass in, validateStudent can only change its replica. Now Student is passed as a reference and declared as a const. Otherwise, the caller must worry that validateStudent has changed the Student they passed in.

Passing parameters by reference also avoids the slicing problem ). When a derived class object is passed as a base class Object (value passing method), the copy constructor of the base class is called, however, the special properties that make the object behavior like a derived class object are cut off, leaving only one pure base class object, for example, suppose you work on a group of classes that implement a graphic Window System:

Class Window {
Public:
...
Std: string name () const; // return window name
Virtual void display () const; // display the window and its content
};

Class required wwithscrollbars: public Window {
Public:
...
Virtual void display () const;
};

All Window objects have a name (name function), and all windows can be displayed (display function ). The fact that display is virtual clearly tells you that the display method of the Window object of the base class may be different from the display method of the specialized Window wwithscrollbars object. Now, suppose you want to write a function to print the name of a window and then display the window. The following is an error example:

Void printNameAndDisplay (Window w) // incorrect! Parameters may be cut
{
Std: cout <w. name ();
W. display ();
}

Consider what will happen when you call this function using a javaswwithscrollbars object:

Descriwwithscrollbars wwsb;

PrintNameAndDisplay (wwsb );

The parameter w will be constructed as a Window object -- it is passed as a value, and the wwsb will be cut off as special information of a wide wwithscrollbars object. In printNameAndDisplay, regardless of the type of the object passed to the function, w will always behave like a Window object (because its type is Window ). Therefore, calling display in printNameAndDisplay always calls Window: display. It is never called using wwithscrollbars: display. The method to bypass the disconnection problem is to pass w in passby reference-to-const mode:

Void printNameAndDisplay (const Window & w)
{// The parameter will not be cut
Std: cout <w. name ();
W. display ();
}

The type of the input window is shown in w. It is very typical to use pointers for reference. Therefore, pass by reference usually means passing a pointer. It can be concluded that if you have a built-in type object (an int), it is often more efficient to pass the value in the way of passing the reference; the same recommendation applies to iterators and function objects in STL.

A small object does not mean that calling its copy constructor is cheap. Many objects (including most STL containers) contain more than one pointer, but copying such objects must copy everything they point to at the same time, which will be very expensive. Even if a small object has a cheap copy constructor, there will be performance problems. Some compilers do not treat built-in and user-defined types equally, even if they have the same underlying representation. For example, some compilers refuse to put an object consisting of only one double into a register, even if they prefer to put a pure double in it. When this happens, it is better to pass such an object as a reference, because the compiler naturally puts a pointer (referenced implementation) into a register.

A small user-defined type is not necessarily another reason for waiting for the selected value to be passed: as a user-defined type, its size often changes. Generally, you can reasonably assume that only the built-in type and the iterator and function object in STL are of low-cost data transfer. For any other type, replace pass-by-value with pass-by-reference-to-const.

· Replace pass-by-value with pass-by-reference-to-const whenever possible. The former is more efficient and can avoid disconnection problems.

· This rule does not apply to built-in types and the iterator and function object types in STL. For them, pass-by-value is generally more suitable.

 



From pandawuwyj's column

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.