Bjarne Stroustrup C ++ style and technical common issues and answers (Edition 1)

zhaozj2021-02-08  252

Bjarne Stroustrup

C style and technical common issues and answers (Explored) Recently Ckeer work is very busy, I am really sorry to care about my friend ... Sincere apologize ......................................................... Style and technology problem. If you have better questions and suggestions, please send it to bs@research.att.com. To know that I can't spend all the time to update my webpage. For more common problems, see General FAQ. Terms and Concepts, see C Glossary. Remember that this is just some questions and answers. Not allowing you to choose an example and explanation in a good book. Some instructions may not be as accurate as those in the reference manuals and standards. Problems about C design You can go to the Design and Evolution of C to see. The use of C and its standard library can be found to see the C Programming Language.

Why do I compile it? Your compiler may have problems. Maybe it is too old, or if there is a problem, it is also possible that your computer is outdated. If so I can't do it. However, this looks more like the program you want to compile. Therefore, when compiled, the compiler has to check the number of hundreds of header files and thousands of lines. In principle, this is avoidable. If the problem is in your library developer design, you can't do anything (except for a better library), but you can build your code structure to reduce the re-compilation after each change. time. A design that can reflect good object relationships is usually always good, maintenance is good. Consider a classic example of programming as follows: Class Shape {

Public: // Shapes user interface

Virtual void Draw () const;

Virtual Void Rotate; Int De Derees

// ...

protected: // General Data Implementation

Point center;

Color color

// ...

}

Class Circle: Public Shape {

PUBLIC:

Void Draw () Const;

Void rotate (int) {}

// ...

protected:

int RADIUS;

// ...

}

Class Triangle: Public Shape {

PUBLIC:

Void Draw () Const;

Void Rotate (int);

// ...

protected:

Point A, B, C;

// ...

}

The idea is that the user manipulates the Shape object through the public interface, and the implementation of the derived class (such as Circle and Triangle) sharing the features represented by the protection member.

Defining shared features that are useful to all subclasses is not easy. The reason is that the protection members seem to be much better than the public interface. For example, although it can be argued that "Center" is exist for all Shape. But forced to maintain a center point of a ▲ is very troubles - for ▲, calculating most of which is meaningless, unless someone has special interest. Protection members seem to be more dependent on the specific implementation of user Shape, but this dependency does not exist. For example, the definition of "Most?) Code and" color "using Shape is logically independent, but the existence of Color in the Shape definition makes it usually necessary to compile the header file that describes the operating system to color definitions. When certain parts of the protective body changes, the user's Shape has to recompile - although only derived classes can access these protection members.

Therefore, these "useful information for" to the part "in the base class" also acts as a user interface. This is the instability of derived class; (when changing information in the base class) is not logically recompiled user code; and over-inclusion of header files in user code (because "for" useful "to realize the information" requires these The root of the header file). Sometimes we call this phenomenon as "BRITTLE BASE CLASS Problem" ("deadly base class problem"). The solution is clearly the "useful information to" use "to the school" "to the school". This is to say: only generate an interface, pure interface. That is, the pure virtual basis of the interface.

Class shape {

Public: // Shape user interface

Virtual void Draw () const = 0;

Virtual Void Rotate (int De De degRees) = 0;

Virtual point center () const = 0;

// ...

// no data

}

Class Circle: Public Shape {

PUBLIC:

Void Draw () Const;

Void rotate (int) {}

Point center () const {return center;}

// ...

protected:

Point cent;

Color color

int RADIUS;

// ...

}

Class Triangle: Public Shape {

PUBLIC:

Void Draw () Const;

Void Rotate (int);

Point center () const;

// ...

protected:

Color color

Point A, B, C;

// ...

}

The user is now separated from the change of derived class. I have found this technology to make the compilation time a decrease in order.

But what should I do when all derived classes (or just part of the derived class) do you need some information? Simply package this information in another class, and then inherit this time when derived class:

Class shape {

Public: // Interface to users of shapes

Virtual void Draw () const = 0;

Virtual Void Rotate (int De De degRees) = 0;

Virtual point center () const = 0;

// ...

// no data

}

Struct Common {

Color color

// ...

}

Class Circle: Public Shape, Protected Common {

PUBLIC:

Void Draw () Const;

Void rotate (int) {}

Point center () const {return center;}

// ...

protected:

Point cent;

int RADIUS;

}

Class Triangle: Public Shape, Protected Common {// Translator Note: Oh, inherits. Oh .... BCB does not support ... :(

PUBLIC:

Void Draw () Const;

Void Rotate (int);

Point center () const;

// ...

protected:

Point A, B, C;

}

Why is the empty class size not zero?

This is to ensure that two different objects have different addresses. For the same reason, NEW always returns a unique object pointer. Consider the following code:

Class Empty {};

Void f ()

{

EMPTY A, B;

IF (& a == & b) cout << "Impossible: Report Error to Compiler Suppil / N is not possible: Quickly report to your compiler manufacturer!"; EMPTY * P1 = New Empty

EMPTY * P2 = New Empty

IF (p1 == p2) cout << "Impossible: Report Error To Compiler Supplier / N is not possible: Quickly report to your compiler manufacturer!"

}

Regarding the empty group, there is a fun rule, that is, the empty group does not need to use one byte representation separately:

Struct x: Empty {// Translator Note: Inheriting from the EMPTY base class

Int a;

// ...

}

Void f (x * p)

{

Void * p1 = p;

Void * p2 = & p-> a;

IF (p1 == p2) cout << "Nice: good Optimizer / N is very good: good optimization;

}

This optimization is safe and is useful. It allows programmers to use empty classes to describe very simple concepts without overloading. There are already some compilers to provide this optimization called "EMPTY BASE CLASS OPTIMIZATION". "Translator Note: BCB provides optimization of empty base, DEV C does not seem to ......."

Why have I have to put the data in my definition section?

You should not do this. If your interface does not require data, do not place them in the interface definition class. It should be placed in the derived class. See why do my compiles take so long?.

Sometimes you have to describe the data in the class. Consider the Complex class:

Template Class complex {

PUBLIC:

Complex (): Re (0), IM (0) {}

Complex (Scalar R): RE (R), IM (0) {}

Complex (Scalar R, Scalar I): RE (R), IM (i) {}

// ...

Complex & Operator = (Const Complex & A)

{RE = A.RE; IM = a.im; return * this;}

// ...

Private:

Scalar RE, IM;

}

The complex class is designed to be used as a system built-in type. Here you must create a real local object (such as: object allocation in the stack, not in the stack) The description in the class declaration is necessary. This ensures the correct intraline of the simple operation. Local objects and inner containers are required to achieve performance similar to system built-in multiplex types.

Why is the member function default is not virtual (Virtual)?

Because many classes are not used as base classes. See Class Complex for example.

At the same time, the objects with a virtual function include additional space. This is the virtual function call mechanism required - is usually an object a Word size. This expense is meaningful, but it also makes it complicated to planned data from other languages. (Such as: C and Fortan)

See THE Design and Evolution Of C You can get more information about reasonable design.

Why is the destructor default not virtual (Virtual)?

Because many classes are not designed to use as base classes. The virtual function is only meaningful in the interface class used as a derived object (usually allocated in the heap, and accessed by reference pointer).

Therefore, when do you need a function of a false argument? When the class contains at least one virtual function. The virtual function means that this class is used as an interface of the derived class. At this time, a derived class object may be destroyed by the base class pointer. For example: Class base {

// ...

Virtual ~ base ();

}

Class Derived: public base {

// ...

~ Derived ();

}

Void f ()

{

Base * p = new deerid;

Delete P; // The false argument function is used to ensure that the derived classification function is called ~ Derived

}

If the analyte function of the base class is not a virtual, the secting function of the derived class cannot be called - this side effect is obvious, and the resource allocated by the derived class is not released. "Translator Note: This is also the destructive function of all TOBJECT classes in the BCB must declare the reason. "

Why is there no fictional function?

Music call is a mechanism that works in the event of only some information. In particular, we will call a function that only knows the interface without knowing its accurate object type. But to create an object you need all information, especially the exact type of the object. Therefore, the constructor cannot be empty.

But there is still a technology that can create objects like "fictional creation functions". Example See TC PL3 15.6.2.

The following example is a technology using an abstract class to generate an appropriate type of object:

Struct f {// Object Create a function of the function

Virtual a * make_an_a () const = 0;

Virtual B * Make_a_b () const = 0;

}

Void User (Const F & FAC)

{

A * p = fac.make_an_a (); // Generate an appropriate type of object A

B * q = fac.make_a_b (); // Generate an appropriate type of object B

// ...

}

Struct fx: f {

A * make_an_a () const {return new ax ();} // AX from a inheritance

B * Make_a_b () const {return new bx ();} // BX inheritance from B

}

Struct fy: f {

A * make_an_a () const {return new aY ();} // AY inherits from A

B * Make_a_b () const {return new by ();} // by inheriting from B

}

int main ()

{

User (fx ()); // This user generates AX and BX

User (fy ()); // This user generates AY and BY

// ...

}

This change is often referred to as "The Factory Pattern" plant mode. The key is that user () will completely isolate class information such as AX and AY.

Why do you have no overloading?

This issue (there are many changes) usually as this is as follows:

#include

Using namespace std;

Class b {

PUBLIC:

INT F (INT i) {cout << "f (int):"; Return i 1;}

// ...

}

Class D: public b {

PUBLIC:

Double F (Double D) {cout << "f (double):"; Return D 1.3;}

// ...

}

int main ()

{

D * pd = new d;

B * Pb = Pd;

COUT << PD-> F (2) << '/ n';

COUT << PD-> F (2.3) << '/ n';}

operation result

: f (double): 3.3

f (double): 3.6

Not like some people

(error

Imagination:

f (int): 3

f (double): 3.6

In other words, no overload analysis occurs between D and B. The compiler is found in the scope of D and discovers the unique function "Double F (Double)" and calls it. It will never bother B (package). In C , there is no overloaded domain - the derived scope is not exception. (See D & E or TC PL3) for details.

But how do I overload all F () from my base class and derived class? It is easy to do with the USING statement.

Class D: public b {

PUBLIC:

Using b :: f; // makes all f from B available

Double F (Double D) {cout << "f (double):"; Return D;

// ...

}

The output at this time will be

f (int): 3

f (double): 3.6

That is to say, overload analysis is applied simultaneously to the F () and D of B, and selects the most suitable F ().

Can I call a virtual function in the constructor?

Yes, but you must be careful. It may not run as you think. In the constructor, the virtual modulation mechanism is disabled. The reason is that the overload from the derived class has not happened. The method of calling the base class first, "Base Before Derived" is first called, and the base class is derived.

Consider the following code:

#include

#include

Using namespace std;

Class b {

PUBLIC:

B (Const string & ss) {cout << "b constructor / n"; f (ss);}

Virtual void f (const string &) {cout << "b :: f / n";}

}

Class D: public b {

PUBLIC:

D (Const String & SS): B (SS) {cout << "D Constructor / N";

Void F (const string & ss) {cout << "D:: f / n"; s = ss;}

Private:

String S;

}

int main ()

{

D ("Hello");

}

The program compilation is as follows

B Constructor

B :: F

D Constructor

Note, no d :: f. Think about what if you change the rules. B :: b () will call D :: f (). Since the constructor D :: D () has not yielded, D:: f () attempts to assign parameters to the string S without initialization. The most likely result is that the program immediately crashes.

The sequence of sectors follows the order of "Derived Class Before Base Class" (derived prior class "). Therefore, the behavior of the virtual function is the same as the constructor. Only local definitions are used - the overloaded function is not called to avoid contact with the (already destroyed).

See D & E 13.2.4.2 or TC PL3 15.4.4.

This rule looks like a human plus. But not this. In fact, it is easy to implement the rules that the constructor calls the virtual function is easy to implement with other functions. However, doing so also suggests that you can't write any virtual functions that depend on the constant created by the base class. It is a terrible confusion.

转载请注明原文地址:https://www.9cbs.com/read-2229.html

New Post(0)