(At this point, GOTW1 ~ 30, "Exceptional C " prototype, complement.)
HERB SUTTER published by March 1998 "What's in a class - the interface principle", "Exceptional C " ITEM 32 ~ 34.
Interface Principle (The Interface Princi)
What is the class? - Interface principles
This Article Appeared IN C Report, 10 (3), March 1998.
I started in a simple and confusing question:
What is the L class? That is, what make up the class and its interface?
Deeply problem is:
L How it is related to C style-oriented programming?
l How do it linked with C Koenig Lookup? And what connection is called MYERS EXAMPLE? I will discuss it.)
l How do it affect our dependence relationship between Class and design object model?
So, "What is the Class?"
First, review a traditional definition of a Class:
Class (definition)
A class describes a set of data and functions that operate these data.
Programmers often unintentionally misinterpreted this definition, change to: "Oh, a class, that is, something in its defined body - member data and member functions." So things turned because it limits "function" For just "member functions". look at this:
// *** EXAMPLE 1 (a)
Class x {/*...*/};
/*...
Void f (const X ";
The question is: Is F is part of X? Some people will answer "No" immediately because F is a non-member function (or "free function"). Others recognize the key things: if the code of Example 1 (a) appears in a header file, that is:
// *** EXAMPLE 1 (b)
Class x {/*...//
PUBLIC:
Void f () const;
}
Consider it again. Do not consider access to Note 1, F is the same, accept a pointer or reference pointing to X. But now this parameter is implicit. So, if Example 1 (a) appears in the same header file, we will find that although f is not a member function of X, it is strongly dependent on X. I will show the true meaning of this relationship in the next section.
For a way, if X and f do not appear in the same header file, then f is just a user function that has already existed, not part of the X (even if f requires an X-type real parametery). We just write a function like a routine, and its type is from the library function, which is clear, our own function is not part of the class in the library function.
The Interface Principle
Interface Principle (Interface Princi)
Relying on that example, I will propose interface principles:
Interface principle
For a class x, all functions, including free functions, as long as they meet
(a) "mention" x, and (b) provide "with the X"
Then it is the logical component of the X because they form an X-connected interface.
Depending on the definition, all member functions are the "components":
(a) Each member function must "mention" x (non-Static member function has an implicit parameter, the type is x * (WQ: C standard, accurately, should be x * const) or const X * (wq: const x * const); the member function of Static is also within the scope of the X); and
(b) Each member function is provided with the x "same period" (in the defined body of X).
Using the principle of interface to analyze Example 1 (a), the conclusions are the same as our initial view: very clear, f "mention" X. If F is also provided with the x "same period" (for example, they exist in the same header file and / or namespace "Note 2), according to the principle of interface, F is the logic component of X, because it is the interface of X Part.
The principle of interface is a useful tributite when it is a component of a Class. Did the free function determined as an integral part of a class, is this violation of intuition? So, give this example, add some weight, change the f more common:
// *** EXAMPLE 1 (C)
Class x {/*...*/};
/*...
Ostream & Operator << (Ostream &, Const X &);
Now, the basic concept of the principle of interface is very clear, because we understand what this special free function is going on: if the Operator << and X "is provided" (for example, they exist in the same header file and / or namespace Middle), then Operator << is the logical component of X because it is part of the interface of X.
According to this, let's go back to the traditional definition of Class:
Class (definition)
A class describes a set of data and functions that operate these data.
This definition is very correct because it does not say "function" is "member".
This interface principle is the principle of OO, or is it just a C unique principle?
I used C terms such as "Namespace" to describe the meaning of "providing" with the same period ", the interface principle is unique to C ? Or is it an OO pass principle and is applicable to other languages?
Consider this very similar example, from another language (actually, is a nono language): C.
/ *** EXAMPLE 2 (a) *** /
Struct_iobuf {/*...data goes here ... * /};
Typedef struct _iobuf file;
FILE * FOPEN (Const Char * FileName,
Const char * mode);
Int fclose (file * stream);
Int fseek (file * stream,
Long offset,
Int Origin;
Long Ftell (File * Stream);
/ * ETC. * /
This is a typical "processing technique" that implements OO code in a language without the language: Provides a structure to inclose the object's data, providing a function - definitely non-member-accepting or returning a pointer to this structure. These free-function constructs (FCLOSE) and operations (FSEEK, FTELL, etc. ...). This trick is a shortcoming (for example, it relies on the user who can hold back the data), but it "is" OO code - in short, a class is a set of data and operations of these data. Although these functions are non-members, they are still part of the FILE interface.
Now, consider how to use Example 2 (a) with a language with Class:
// *** EXAMPLE 2 (b)
Class file {
PUBLIC:
File (const char * fileename,
Const char * mode);
~ File ();
INT FSEEK (Long Offset, int Origin);
Long ftell ();
/ * ETC. * /
Private:
/ 10/200 goes here ... * /
}
The parameter of that file * becomes an implicit parameter. Now it is clear, FSeek is part of the File, and it is not the same as the member function in Example 2 (a). We can even implement the function as part of the member function, part of the non-member function:
// *** EXAMPLE 2 (C)
Class file {
PUBLIC:
File (const char * fileename,
Const char * mode);
~ File ();
Long ftell ();
/ * ETC. * /
Private:
/ 10/200 goes here ... * /
}
Int fseek (file * stream,
Long offset,
Int Origin;
Very clear, is whether the member function is not the key to the problem. As long as they "mention" File "is provided in the same period", they are components of the File. In Example 2 (a), all functions are non-member functions because they can only do this in the C language. Even in C , some must (or should) is a non-member function: Operator << Can't be a member function, because it requires a stream object as left operation parameters, Operator should not be a member function to allow left Operation parameters occur (automatically) type conversion.
Introduction Koenig Lookup
The principle of the interface has a deepest significance. If you realize that it is with Koenig Lookup, I'm 4〗. Here, I will use two examples to explain and define Koenig Lookup. In the next section, I will use myers example to demonstrate why it is directly related to the principle of interface.
This is why we need Koenig Lookup, from the example of C standards:
// *** EXAMPLE 3 (a)
Namespace ns {
Class T {};
Void f (t);
}
Ns :: t parm;
Int main () {
F (PARM); // ok: Calls ns :: f
}
Very beautiful, isn't it? "Obvious", the programmer does not need to be written as NS :: F (PARM), because F (PARM) "clear" means ns :: f (parm), right? However, things that are obvious to us are not always obvious for compilers, especially considering that "Using" that connects the name f into the code space is useless. Koenig Lookup allows the compiler to complete the right thing. It works so: the so-called "name search" is when you write a "F (PARM)" call, the compiler must decide to make a function you want to call F. (Due to the reasons for overloading and scope, there may be a function called F.) Koenig Lookup says this, if you pass a Class type in a Class type (here is PARM, type ns :: T), in order to find this function name, the compiler is required to not only search for a regular space such as a local scope, but also search for a namespace containing the real parameter type (here is NS) 〖Note 5. Thus, in Example 3 (a) is this: Parameters passing to f, T, T definitions in Namespace NS, the compiler should consider the f - not very surprised in Namespace NS.
You don't have to define F is a good thing, because so we are easy to limit the function name:
// *** EXAMPLE 3 (B)
#include
#include
// Declares the free function
// std :: operator << for strings
Int main () {
Std :: string hello = "hello, world";
Std :: cout << Hello; // OK: Calls
} // std :: operator <<
In this case, there is no Koenig Lookup, the compiler will not find Operator <<, because we expect Operator << is a free function, we only know that it is part of the String package. If the programmer is forced to limit this function name, it will be very unhappy because the last line cannot be used naturally. Instead, we must write as "std :: operator << (std :: cout, hello);" or "Using Namespace Std;". If this situation touches you, you will understand why we need Koenig Lookup.
Summary: If you provide a Class and a "mention" in the same namespace, "refer to" this Class's free function 〖Note 6, the compiler establishes a strong association between the two. Let us return to the interface in principle, consider this MYERS EXAMPLE:
More Koenig Lookup: MYERS EXAMPLE
Consider the first (slightly) simplified example:
// *** EXAMPLE 4 (a)
Namespace ns {// typical from some
Class T {}; // HEADER T.H
}
Void f (ns :: t);
Int main () {
Ns :: t parm;
F (PARM); // ok: Calls Global F
}
Namespace NS provides a type T, and a full-class function f is provided outside, and this function is accepted to accept one T. Very good, the sky is a blue, the world is full of peace, everything is very beautiful. The clock is ticking. One day, NS author is based on the need to add a function:
// *** EXAMPLE 4 (B)
Namespace ns {// typical from some
Class T {}; // HEADER T.H
Void f (t); // <- new function
}
Void f (ns :: t);
Int main () {
Ns :: t parm;
F (PARM); // Ambiguous: ns :: f
} // or global f?
Add a function of a function "destroy" in the namespace, the code outside the namespace, even if the user code does not use "Using" to bring the name in the NS to its own scope! But wait, things are getting better - Nathan MYERS 〖Note 8 points points out interest in named space and Koenig Lookup:
// *** the myers example EXAMPLE: "before"
Namespace a {
Class x {};
}
Namespace b {
Void f (a :: x);
Void G (a :: x parm) {
F (PARM); // ok: Calls b :: f
}
}
Very good, very blue .... One day, the author of A is based on the need to add another function:
// *** the myers example: "after"
Namespace a {
Class x {};
Void f (x); // <- new function
}
Namespace b {
Void f (a :: x);
Void G (a :: x parm) {
F (PARM); // Ambiguous: A :: F or b :: f?
}
}
"Ah?" You may ask. "The namespace selling point is to prevent the name conflict, isn't it? However, adding functions in a namespace seems to" destroy the code in another completely isolated namespace. "Yes, the code in Namespace B is Destroyed, just because it "mention" a type from NameSpace A. The code in B did not write "Using Namespace; A" anywhere, nor did it write "use a :: x;".
This is not a problem, B is not "destroyed". In fact, this is the correct behavior that should happen. Note 9. If there is a function f (x) in the namespace in X, then, according to the interface principle, F is part of the interface of the X. f is a free function is not a key; if you want to confirm that it is still x's logical component, just give it another name:
// *** restating the myers example: "after"
Namespace a {
Class x {};
Ostream & Operator << (Ostream &, Const X &);
}
Namespace b {
Ostream & Operator << (Ostream &, Const A :: X &);
Void G (a :: x parm) {
Cout << Parm; // Ambiguous:
} // A :: Operator << OR
} // b :: Operator << • If the user code provides a "referred to" function, the call will have an amphony when signing a function signature provided by the namespace in X. B must explicitly indicate which function it wants to call itself, it is still provided with the X ". This is what our expectations should provide things:
Interface principle
For a class x, all functions, including free functions, as long as they meet
(a) "mention" X, and
(b) Provided with the X "
It is the logical component of the X because they form an X-connected interface.
In short, the principle of interface is the same as Koenig Lookup's behavior is not an accident. Koenig Lookup's behavior is based on the principle of interface.
(What is the "related to the relationship?" This section shows why the membership function class is still more than the associated relationship than the non-member function)
How strong is the association of "Component"?
Although Interface Principle illustrates members and non-member functions to be a logic "component", it does not say that members and non-members are equal. For example, the member function automatically gets all access rights inside the Class, and the non-member function can only get the same permissions when they are declared as a friend. Similarly, in the name search (including Koenig Lookup), the C language special indicates that the member function is more than the associated relationship between the non-member function:
// *** NOT The Myers Example
Namespace a {
Class x {};
Void f (x);
}
Class b {
// Class, Not Namespace
Void f (a :: x);
Void G (a :: x parm) {
f (PARM); // ok: b :: f,
// not Ambiguous
}
}
What we now discussed is Class B instead of NameSpace B, so this doesn't have a second sense: When the compiler finds a member function called F, it does not use Koenig Lookup to search for free functions.
So, in two main issues - Access Rules and Name Search Rules - Even according to the principle of interface, when a function is an integral part of a class, the member function is more than the non-member function than the non-member function. relationship.
What does a Class depend on?
"What is the Class inside" is not just a philosophical problem. It is a rootic problem, because without the correct answer, we cannot properly analyze the dependencies of Class.
In order to prove this, look at this seems irrelevant question: How to implement a class Operator < There are two main methods, but they all have hits. I have conducted an analysis. In the end we will find that we have returned to the interface, it provides an important guiding principle when it is correctly analyzed.
The first:
// *** EXAMPLE 5 (a) - nonvirtual streaming
Class x {
/ 9.Ostream is never exercioned here ... * /
}
Ostream & Operator << (Ostream & O, Const X) {
/ * Code to Output An x to a stream * /
Return O;
}
This is the second:
// *** EXAMPLE 5 (b) - Virtual streaming
Class x {/*...//
PUBLIC:
Virtual Ostream & Print (Ostream & O) {/ * code to output an x to a stream * /
Return O;
}
}
Ostream & Operator << (Ostream & O, Const X) {
Return X.Print ();
}
In both cases, Class and functions appear in the same header file and / or namespace. Which one do you choose? What is it? It has always been analyzed in this way with this way:
l The advantage of selecting (a) is to have lower dependence. Because X does not have member functions "mention" ostream ", x (look) does not depend on stream. Selection (a) also avoids an overhead of an additional virtual function call.
l Select (b) The benefits of selecting all X-derived classes can be correctly Print, even if it is passed to Operator << is an X &.
This analysis is flawed. Using the principle of interface, we can find why - the benefits of selecting (a) are illusion because:
l According to the principle of the interface, as long as the operator << "mentioned" X (in both cases) and is provided with the X "in the same period" (in both cases), it is the logical component of the X.
L in both cases, Operator << "all" mention "the stream, so Operator << Dependence.
l Because the Operator << is the logical component of the X and the stream is dependent on the stream, so X is dependent on the stream.
Therefore, our main benefits of the choice (a) will not exist at all - two cases depend on the stream! If (usually also) Operator << and X appears in the same XH, the implementation of X in both cases and user modules that use X-based entities are rely on flow, and at least the forward statement of the stream is required to complete compilation .
With the sintering of the fiction of the first bigity, choose (a) only there is no advantage to call overhead in the virtual function. If we do not use the principle of interface, we can't easily analyze the dependence of the truth (and the factual property).
Bottom line: Divide members or non-members do not have much meaning (especially when analyzing dependencies), and this is exported to the principle of interface.
Some interesting (even surprised) results
Typically, if A and B are CLSS, and F (A, B) is a free function:
l If the A is provided in the same period, then f is an integral part of A, and A will depend on B.
l If b is provided in the same period, F is part of B, and B will depend on A.
l If A, B, F is provided in the same period, then f is part of A and B, and A and B are cyclic dependencies. This has a fundamental meaning - if the author of a library provides two CLASS and the operation involving both, then this action is probably in order to be used synchronously. Now, the interface principle gives this loop depending on a strict proof.
Finally, we have come to a really interesting state. Typically, if A and B are Class, and A :: g (b) is a member function of A:
l Because A :: g (b) exists, it is obvious that A is always dependent on B. There is no doubt. l If A and B are provided in the same period, then A :: g (b) and B are of course also provided in the same period. Therefore, since A :: g (b) is satisfied with the "B and B" in the same period ", according to the principle of interface (I am afraid some surprises): A :: g (b) is part of B, but because A :: g (b) uses a (implied) A * parameter, so B relies on A. Because A is also dependent on B, this means A and B cycle dependence.
First, it looks only to "determine the component function of a Class is also an integral part of another class", but this is only established when the A and B are provided. Think about it: If A and B are provided in the same period (that is, in the same header), and A mentioned B, "intuition" in a member function also tells us A and B I probably the loop dependence . It is definitely strong coupled between them, and their facts that provide and mutual effects means that: (a) They should be used synchronously, (b) Change one will also affect the other.
The problem is: Before this, in addition to "intuition", it is difficult to use physical to prove the cyclic dependence between A and B. Now, this loop depending on the principle of the interface can be derived.
Note: Unlike Class, Namespace does not need to be completed once, this "same offer" depends on the current visible part of Namespace:
// *** EXAMPLE 6 (a)
// --- File a.h ---
Namespace n {class b;} // forward Decl
Namespace n {class a;} // forward Decl
Class N :: a {public: Void g (b);
// --- File B.H ---
Namespace n {class b {/*...*/};
A user includes A.h, so A and B are the same as the same period and is loop dependent. B users contain B.h, so A and B are not provided in the same period.
to sum up
I hope you get 3 ideas:
l Interface Principle: For Class X, all functions, including free functions, as long as it meets (a) "mentioning" X, (b) and x ", it is the logical component of X, because they are X Part of the interface.
l Therefore, members and non-member functions are logical components of a Class. However, the member function has a stronger relationship than non-member functions.
l In the interface principle, the most useful explanation of the "same period" is "appearing in the same header file and / or namespace". If the function appears in the same header file in the same header file, it is an integral part of this Class. If the function is in the same namespace, when the object references and name searches are searched, it is part of this Class.
Note 1. Even if the initial F is a friend, the situation is still the case.
Note 2. We will discuss the relationship between namespaces in detail in the subsequent space later, as it indicates that the principle of the interface is actually consistent with Koenig Lookup.
Note 3. Similarity between members and non-member functions, exhibited even more intensified in other overloaded operators. For example, when you write "A B", you can call A.Operator (b) or Operator (A, B), depending on the type A and B.
Note 4. Named Andrew Koenig, because of this definition initially, he is a long-term member of AT & T'S C Team and C Standards Committee. See "Ruminations on C " (Addison-Wesley, 1997) written by A. Koenig and B. Moo. Note 5. There are other details, but the essence is this.
Note 6. By transmitting values, pass references, pass pointers, or other methods.
Note 7. To acknowledge that its relationship is weaker than the association between Class and its members. There is "more than a strong relationship" after the following article.
Note 8.Nathan is also a long-term member of C Standards Committee, and is the main author of the standard Locale facility.
Note 9. This special example appears at the Morristown meeting of November 1997, which arouses me to think about member relationships and dependencies. MYERS EXAMPLE is very simple: namespace is not as unusually imagined, but they have perfectly perfect in isolation and just satisfy their job.