"Effective C + +" design and declaration: Clause 18-clause 19

Source: Internet
Author: User

Software design is the "Let the software do what you want it to do" steps and practices. Start with a general idea, gradually clear, structure the details, and finally design a good interface (interface). These interfaces then become declarations of C + +.

The following is an approach to interface design and declaration. A very important criterion for designing interfaces is to make the interface easy to use and not to be misused easily. This is a large guideline that includes a lot of content after refinement: correctness, efficiency, encapsulation, maintainability, ductility, and agreement consistency.


Article 18: Make the interface easy to use correctly, not easy to misuse

Interfaces are the only means of exchange between the customer and your code. It's nice if the customer uses the interface you've developed correctly, but if your interface is misused, you'll have to take part of the blame. Ideally, if a client uses an interface and does not get the behavior he wants, it should not be compiled, and if the build passes, then the customer should get the behavior he wants.

To develop an interface that is "easy to use and not easy to misuse," first consider what kind of errors a customer might have. If a class is designed to represent a date, how should its constructor be designed?

Class date{public:void Date (int month, int day, int year), ...};

this interface appears to be correct (in the United States that the date is a month-day-year), but the customer may still make a mistake. For example

Date d (30, 3, 1995);//The month and day are reversed
And also

Date D (2, 30, 1995);//2 Month No. 30th

The above example looks foolish, but it may still appear.

Errors that customers might enter incorrectly can be prevented by introducing new data types like this.

struct Day{explict Day (int D): Val (d) {};int val;} struct month{explicit Month (int m): Val (m) {}int val;}; struct year{explicit year (int y): val (y) {}int val;};
In the constructor that overrides date

Class Date{public:void Date (const month& m, const day& D, const year& y), ...};
in this case, the compiler will make an error if the order of month and date is incorrect. However, it is still not possible to avoid the second error when the client is in use.

After the parameter type is positioned, you can limit its value. Only 12 months a year, so month should reflect this fact. One way to do this is to use an enum to represent the month, but the enum does not have type safety because the enum essence is an int type. One way to be more secure is to define all the months in advance.

Class Month{public:static Month Jan () {return month (1);} Static month Feb () {return month (2);} ... private:explicit month (int m);//can only be used inside the class to prevent other months from being generated. ...... };
Replace the object with a function.


Another way to prevent client errors is to restrict what can be done within a type and what can not be done. What is often seen is the addition of the restriction Const. For example, IN clause 3, the return value of the operator* is modified with a const, which prevents the customer from making mistakes due to "user-defined type"

if (a*b=c)//Here is actually going to do the comparison instead of assigning the value

Here's another design principle: make types easy to use and not easily misused .

Unless you have a good reason, you should try to keep your types behavior consistent with the built-in types. For example, a customer already knows what kind of behavior a type such as int is, so you should try to make your types behave the same way in reasonable terms.

Avoid sending conflicts on built-in types, which are intended to provide an interface for consistent behavior. "Consistency" is most likely to cause the interface to be used correctly. The interfaces of STL containers are very consistent (though not perfectly consistent), making them very easy to use. For example, each STL can easily have a member function named size that represents how many objects are in the current container. With this play is Java, which allows you to use the Length property for an array, use the length method for strings, and the same confusion for lists using size method;.net. Its arrays has a property name of length, but ArrayLists has a property named count.


Any interface, if the customer must remember to do something, then this interface has a tendency to use incorrectly, because the customer is likely to forget to do that thing. For example, IN clause 13 there is a factory function factory, which returns a pointer to a dynamically allocated object within the investment inheritance system:

investment* createinvestment ();
To avoid resource leaks, the pointer returned by createinvestment must be deleted so that the customer has two chances of making a mistake: no pointer was removed, or more than one was deleted.

Article 13 end up using smart pointers to ensure the release of resources. But what if the customer forgets to use the smart pointer? Many times, a good interface design principle is preemptive, so that the factory function returns a smart pointer

Shared_ptr<investment> createinvestment ();
This forces the customer to store the return value inside the shared_ptr, almost eliminating the possibility of forgetting to delete the bottom investment object.

SHARED_PTR's interface design prevents a large group of customers from committing a resource leak error. As stated in Clause 14, SHARED_PTR allows a resource release function (the so-called delete, default delete) to be bound to a smart pointer when the smart pointer is created.

Suppose the designer of class expects the client to get investment* pointers from Createinvestment, and eventually passes the pointer to a function named Getridofinvestment, rather than deleting it. Then this interface has a new, misused error: An attempt to replace getridofinvestment with a destructor address. At this point, the designer of Createinvestment can preempt, returning a shared_ptr pointer that binds getridofinvestment to a delete.

This allows the Shared_ptr constructor to accept two arguments: one managed pointer and the other a "delete" that changes to 0 o'clock. This inspires us to create a null shared_ptr and getridofinvestment as its delete.

Shared_prt<investment> createinvestment () {shared_prt<investment> RetVal (static_cast<investment* > (0), getridofinvestment); retval=......;//order RetVal to point to the correct object return retVal; }

if the original pointer managed by RetVal is determined before the retval is created, you can assign a value directly to the constructor without converting the null pointer.

A particularly good property of shared_ptr is that it automatically uses its "every pointer-specific delete", thus eliminating another potential customer's error: Corss-dll problem. This problem occurs when an object is created by new in a DLL in a dynamic link library, but is destroyed in another DLL by the delete. On many platforms, this class of cross-DLL new/delete will result in run-time errors. SHARED_PTR does not have this problem because its removal comes from the delete of the DLL that it was born in.

The purpose of this article is specifically for shared_ptr, so that the interface is easy to use correctly and is not easily misused. Shared_ptr brings so much benefit, how much lower is the cost of using it? The most common shared_ptr implementations come from boost (clause 55), and the source code can be referenced here. It is twice times the size of the original pointer (a raw pointer, a counter) to dynamically allocate memory for use as a record and a delete, to invoke the delete in virtual form, and to be thread-synchronized when a multithreaded program modifies the number of references (thread Synchronization) (can turn off support for multithreading). In short, it is larger than the original pointer and uses secondary memory. But these costs are not significant and the benefits are very effective.

Summarize:

1, good interface is easy to use correctly, it is not easy to misuse.

2. The "promote correct use" approach includes interface consistency and is compatible with built-in types.

3. The "Prevent misuse" method includes establishing new types, restricting operations on types, binding object values, and eliminating customer resource management responsibilities.

4. The shared_ptr supports a specific removal device. Can guard against Cross-dll problem.

Article 19: Design class like design type

In an object-oriented language, when a new class is defined, a new type is defined. When developing C + +, much of the time is in the expansion type system , which means that the programmer is just the designer of the class or the designer of the type. Overloaded functions and operators, memory allocation for deallocation, creation and destruction of objects ... These are all controlled by the programmer. Therefore, the design of class should be discussed with the same caution as when the language designer originally designed the language built-in type.

When designing a new class, you should answer the question.

How should objects of the new type be created and destroyed?

This affects the class's constructors and destructors, as well as the allocation and deallocation of memory.

What is the difference between object initialization and object assignment?

This is the behavior of your constructor and assignment operators and their differences. Do not confuse initialization and assignment, which correspond to different functions (see Clause 4).

What does the object of the new type mean if it is pass by value?

This is determined by your copy constructor.

What is the legal value of the new type?

For member variables of class, only some datasets are usually valid. These datasets determine the constraints your class wants to maintain, and it also determines the work of some of your member functions for error checking, and it also affects the details of the function throwing exceptions.

Does the new type need to match an inheritance graph (inheritance graph)?

If you inherit from certain existing classes, in particular the effects of the virtual or non-virtual (clause 34 and clause 36) that are received by those classes. If you allow other classes to inherit your class, it will affect the functions you declare, especially destructors (whether virtual).

What kind of conversion does the new type require?

Does your type exist between the other type and need to convert between the behaviors? If you allow type T1 to be converted to type T2, you must write a type conversion function operator T2 within class T1, or write a constructor for T2 (which can be called by a single argument) within class non-explicit-one-argument. If only the explicit constructor is allowed to exist, you must write out a function specifically responsible for performing the conversion, and not be a type conversion operator (Type-conversion operators) or Non-explicit-one-argument constructor. Clause 15 shows an example of implicit and explicit conversions.

What operators and functions are reasonable for this new type?

It depends on what functions you declare for class. Some of them are member functions, some are not.

What kind of function should be dismissed?

That is, which functions should be declared private.

Who should take a member of the new type?

This question helps you decide which member is public and which is private. It also helps you to figure out which class or fuction should be friends and whether it is reasonable to nest them in another.

What is the "undeclared interface" for the new type (undeclared interface)? (Do not understand??? )

What assurance does it provide for efficiency, exception security (clause 29), and resource utilization (such as multitasking and dynamic memory)? The assurance you provide in these areas will add a response constraint to your class implementation code.

How generalized is your new type?

Perhaps you are not defining a new type, but defining an entire types family. If so, you should not define a new class, but rather define a new class template.

Do you really need a new type?

If you just define a new derived class to add functionality to an existing class, you might be able to define one or more non-member functions or templates to achieve your goal.


The above questions are not easy to answer, and defining an efficient class is a challenge. If you can design a custom class that is as friendly as a C + + built-in type, it's worth the effort.



"Effective C + +" design and declaration: Clause 18-clause 19

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.