Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
programming:improve-your-cpp [2006/12/14 14:41] cyril |
programming:improve-your-cpp [2013/09/19 16:40] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | < | + | \\ This page is based on the excellent books of Scott Meyers : __Effective C++ (50 specific |
- | + | ||
- | \\ This page is based on the excellent books __50 ways to improve your C++__ and __35 more ways to improve your C++__. | + | |
====== Effective C++ ====== | ====== Effective C++ ====== | ||
Line 7: | Line 5: | ||
===== Shifting from C to C++ ===== | ===== Shifting from C to C++ ===== | ||
- | + | === Item 1 : Use const and inline instead of #define === | |
- | ==== Item 1 : Use const and inline instead of # | + | Because constants don't appear in the symbole table which is annoying for debugging, use constant variables (it is as fast) : |
- | Because constants don't appear in the symbole table which is annoying for debugging : | + | < |
- | < | + | |
const float ASPECT_RATIO = 1.653; | const float ASPECT_RATIO = 1.653; | ||
</ | </ | ||
- | And because there are lot of traps with macros : | + | And because there are lot of traps with macros, use inline (templated if necessary) |
- | < | + | < |
template< | template< | ||
</ | </ | ||
- | ==== Item 2 : Prefer iostream to stdio.h | + | Main traps of macros are : |
- | Because of type safety | + | * if you forget to put parentheses around all variable names : |
- | < | + | <code cpp> |
+ | #define sqr(x) x*x // wrong way | ||
+ | sqr(1+2) | ||
+ | #define sqr(x) (x)*(x) // right way | ||
+ | </ | ||
+ | * if you call a function as a macro parameter ('' | ||
+ | |||
+ | Inline functions have none of these traps, and are as fast. | ||
+ | |||
+ | === Item 2 : Prefer iostream to stdio.h === | ||
+ | Because of type safety | ||
+ | |||
+ | To define stream operators in your class : | ||
+ | < | ||
friend ostream& | friend ostream& | ||
ostream& | ostream& | ||
</ | </ | ||
- | ==== Item 3 : Use new and delete instead of malloc and free ==== | + | === Item 3 : Use new and delete instead of malloc and free === |
Because they don't call constructors and destructors ! | Because they don't call constructors and destructors ! | ||
- | ==== Item 4 : Prefer C++-style comments | + | === Item 4 : Prefer C++-style comments === |
Because you can't embed C-style comments in other comments ! | Because you can't embed C-style comments in other comments ! | ||
Line 34: | Line 44: | ||
===== Memory management ===== | ===== Memory management ===== | ||
- | ==== Item 5 : Use the same form in corresponding calls to new and delete | + | === Item 5 : Use the same form in corresponding calls to new and delete === |
- | '' | + | '' |
- | ==== Item 6 : Call delete on pointer members in destructors | + | === Item 6 : Call delete on pointer members in destructors === |
- | Initialize all pointer members in each of the constructors (at '' | + | |
- | Delete for all pointer members the existing memory and assign new memory in the assignment operator | + | |
- | Delete the memory in the destructor | + | |
- | ==== Item 7 : Check the return value of new ==== | + | === Item 7 : Check the return value of new === |
Set an error-handling function, globally : | Set an error-handling function, globally : | ||
- | < | + | < |
void noMoreMemory() { cerr << " | void noMoreMemory() { cerr << " | ||
main() { set_new_handler(noMoreMemory); | main() { set_new_handler(noMoreMemory); | ||
</ | </ | ||
Or for a specific class : | Or for a specific class : | ||
- | < | + | < |
typedef void (*PEHF)(); // PEHF = pointer to error handling function | typedef void (*PEHF)(); // PEHF = pointer to error handling function | ||
Line 82: | Line 92: | ||
- | ==== Item 8 : Adhere to convention when writing new ==== | + | === Item 8 : Adhere to convention when writing new === |
Means having the right return value (pointer or 0), and calling an error-handling function when insufficient memory is avalaible : | Means having the right return value (pointer or 0), and calling an error-handling function when insufficient memory is avalaible : | ||
- | < | + | < |
void * operator new(size_t size) // your operator new could take additional params | void * operator new(size_t size) // your operator new could take additional params | ||
{ | { | ||
Line 99: | Line 109: | ||
</ | </ | ||
If the operator new is in a class X, you should add before to attempt to allocate memory : | If the operator new is in a class X, you should add before to attempt to allocate memory : | ||
- | < | + | < |
if (size != sizeof(X)) return ::new char[size]; | if (size != sizeof(X)) return ::new char[size]; | ||
</ | </ | ||
This occur when you inheritate from the class without rewriting the new operator. | This occur when you inheritate from the class without rewriting the new operator. | ||
- | ==== Item 9 : Avoid hiding the global new ==== | + | === Item 9 : Avoid hiding the global new === |
If you add parameters to your '' | If you add parameters to your '' | ||
- | < | + | < |
class X | class X | ||
{ | { | ||
Line 119: | Line 129: | ||
</ | </ | ||
- | ==== Item 10 : Write delete if you write new ==== | + | === Item 10 : Write delete if you write new === |
You can rewrite '' | You can rewrite '' | ||
- | < | + | < |
class Airplane | class Airplane | ||
{ | { | ||
Line 163: | Line 173: | ||
===== Constructors, | ===== Constructors, | ||
- | ==== Item 11 : Define a copy constructor and an assignment operator for classes with dynamically allocated memory ==== | + | === Item 11 : Define a copy constructor and an assignment operator for classes with dynamically allocated memory === |
+ | Because if you don't, the compiler with use the default ones which are bit-to-bit copies. Thus it will copy pointers, and if you free it in one object, the other one will point to nothing ... | ||
- | ==== Item 12 : Prefer initialization to assignment in constructors | + | |
+ | === Item 12 : Prefer initialization to assignment in constructors === | ||
You don't have choice for constants or reference members, but in general it is more efficient (constructors are always called once, and with initialization all members are written in raw which is faster than assigning them one by one). | You don't have choice for constants or reference members, but in general it is more efficient (constructors are always called once, and with initialization all members are written in raw which is faster than assigning them one by one). | ||
- | ==== Item 13 : List members in an initialization list in the order in which they are declared | + | === Item 13 : List members in an initialization list in the order in which they are declared === |
- | Because it is the order of the declarations which is followed by the compiler, | + | Because it is always |
+ | and never the order of the initialization list, | ||
+ | so you can fall into traps if you don't have the same order (eg you initialize '' | ||
- | ==== Item 14 : Make destructors virtual in base classes | + | === Item 14 : Make destructors virtual in base classes === |
- | Because if you don' | + | Because if you don' |
- | ==== Item 15 : Have operator= return a reference to *this ==== | + | But do it only if it is a base class (you plan to inheritate from it), which generally corresponds to the fact that there is at least one virtual function, because you loose a little performance. |
- | To allow chains | + | |
- | < | + | Tip : if you want to have an abstract class, but you don't have any function to make pure virtual, declare a pure virtual destructor ! (but you have nevertheless to provide a definition for this pure virtual destructor, and you can't declare it inline). |
+ | |||
+ | === Item 15 : Have operator= return a reference to *this === | ||
+ | To allow to chain assignments | ||
+ | < | ||
C& C:: | C& C:: | ||
</ | </ | ||
- | ==== Item 16 : Assign to all data members in operator= ==== | + | === Item 16 : Assign to all data members in operator= === |
Because if you want to assign some, the compiler won't anymore assign the other ones. Moreover if it is a derived class you have also to initialize the data members of the base class : | Because if you want to assign some, the compiler won't anymore assign the other ones. Moreover if it is a derived class you have also to initialize the data members of the base class : | ||
<code cpp> | <code cpp> | ||
<code cpp> | <code cpp> | ||
- | ==== Item 17 : Check for assignment to self in operator= ==== | + | === Item 17 : Check for assignment to self in operator= === |
- | That's to say, always begin by : '' | + | That's to say, always begin by : |
+ | <code cpp>if (this == &rhs) return *this;'' | ||
+ | |||
+ | Not only because it saves time, but above all because it will create serious problems with freeing and reallocating resources ! | ||
+ | |||
+ | But be careful this test won't work with multiple inheritance because one same object can have different addresses according to the type it is casted to. If so you could add an unique identifier for each object. | ||
Line 192: | Line 215: | ||
- | ==== Item 18 : Strive for class interfaces that are complete and minimal ==== | + | === Item 18 : Strive for class interfaces that are complete and minimal === |
+ | Because clients can do whatever they want to do, but it remains easy to learn (no confusion), easier to maintain, and shorter to compile. In brief, think carefully about whether the convenience of a new function justifies the additional costs. | ||
- | ==== Item 19 : Differentiate among member functions, global functions, and friend functions ==== | ||
- | ==== Item 20 : Avoid data members in the public interface ==== | + | === Item 19 : Differentiate among member functions, global functions, and friend functions |
+ | When in doubt, try to be object-oriented (overloading operators etc). | ||
- | ==== Item 21 : Use const whenever possible ==== | + | But for example if you create a Fraction class and want to implement multiplication. If you overload '' |
- | ==== Item 22 : ==== | + | In the same way, stream operators are always global function, because if they were members the variable should be on the left, like '' |
- | ==== Item 23 : ==== | + | === Item 20 : Avoid data members in the public interface |
+ | Because : | ||
+ | * it is simplier for the user (all members are functions, so there no need to try to remember whether to use parentheses), | ||
+ | * it gives you control over the accessibility of the members, | ||
+ | * it gives you functional abstraction (you can change a data member by computations for example). | ||
- | ==== Item 24 : ==== | ||
- | ==== Item 25 : ==== | + | === Item 21 : Use const whenever possible |
- | ==== Item 26 : ==== | + | You can use it outside of classes for : |
+ | * global constants | ||
+ | * static objects (local to either a file or a block) | ||
+ | Inside classes for : | ||
+ | * static data members | ||
+ | * non static data members | ||
+ | With pointers : | ||
+ | <code cpp> | ||
+ | char | ||
+ | const char *p | ||
+ | char * const p = " | ||
+ | const char * const p = " | ||
+ | </ | ||
+ | And of in function declarations : | ||
+ | * individual parameters | ||
+ | * return value | ||
+ | * function as a whole for member functions (doesn' | ||
- | ==== Item 27 : ==== | ||
- | ==== Item 28 : ==== | ||
- | ==== Item 29 : ==== | ||
- | ==== Item 30 : ==== | + | C++ makes a bitwise test for const member functions |
- | ==== Item 31 : ==== | + | Moreover sometimes you can want to modify a member in a function, but this function is const in the sense that the modifications are undetectable by a client (eg if you want to cache the length of a string, you don't modify the string so the function is conceptually const, but you modify the cache variable so the compiler won't accept it). Fortunately, |
+ | <code cpp> | ||
+ | unsigned String:: | ||
+ | { | ||
+ | String * const localThis | ||
+ | dataLength | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
- | ==== Item 32 : ==== | + | === Item 22 : Pass and return objects by reference instead of by value === |
+ | Problems of returning by value : | ||
+ | * it can cost a lot of time because copy constructors of the object and all sub-objects are called for each copy | ||
+ | * the //slicing problem// can be dangerous : if you have a base class with a virtual function, and a derivated class, if you pass a derivated object to a function that takes a base object by value, it will lose all virtual functions of the derivated object because it is the copy constructor of the base class which is called ! | ||
- | ==== Item 33 : ==== | + | === Item 23 : Don't try to return a reference when you must return an object |
+ | If you have to return a new object (eg for operator+), you must return it by value. Indeed this new object will be created either in the stack and disappear when you go out of the function (=> segfault), or in the heap (with '' | ||
- | ==== Item 34 : ==== | + | === Item 24 : Choose carefully between function overloading and parameter defining |
+ | Both permit to call functions with a different number of parameters. Parameter defining (default values) makes easier to avoid duplication of code. But you can only use it if : | ||
+ | * a reasonable default value exists (and if a generic algorithm exists and is not less efficient that what you could have done by knowing the number of arguments), or | ||
+ | * you can use a //magic number// which tells that the parameter is not set. | ||
+ | To cope with the duplication code problem with overloading, | ||
- | ==== Item 35 : ==== | + | === Item 25 : Avoid overloading on a pointer and a numerical type === |
+ | ie avoid : | ||
+ | <code cpp> | ||
+ | void f(int x); | ||
+ | void f(char *p); | ||
+ | </ | ||
+ | Because there is no ambiguity for the compiler when calling '' | ||
- | ==== Item 36 : ==== | + | === Item 26 : Guard against potential ambiguity |
+ | This code is correct and compiles : | ||
+ | <code cpp> | ||
+ | class A { | ||
+ | | ||
+ | A(const class B&); // constructor from B | ||
+ | }; | ||
+ | class B { | ||
+ | | ||
+ | operator A() const; // cast operator to A | ||
+ | }; | ||
+ | </ | ||
+ | But it can create ambiguities : | ||
+ | <code cpp> | ||
+ | void g(const A&); | ||
+ | B b; | ||
+ | g(b); // error ! - ambiguous : calls constructor from B or cast to A ? | ||
+ | </ | ||
+ | The problem with ambiguities is that you can miss it at the beginning, and if it is the client who is faced to and if he doesn' | ||
+ | This can happened also with '' | ||
+ | <code cpp> | ||
+ | class Base1 { | ||
+ | | ||
+ | int doIt(); | ||
+ | }; | ||
+ | class Base2 { | ||
+ | | ||
+ | void doIt(); | ||
+ | }; | ||
+ | class Derived : Base1, Base2 {} d; | ||
+ | d.doIt() // error - ambiguous | ||
+ | </ | ||
+ | You have to explicitly specify which one of the functions you want to call : '' | ||
- | ==== Item 37 : ==== | + | === Item 27 : Explicitly disallow use of implicitly generated member functions you don't want === |
+ | The compiler automatically generates some functions such as '' | ||
- | ==== Item 38 : ==== | ||
- | ==== Item 39 : ==== | + | === Item 28 : Use structs to partition the global namespace |
+ | Define your global constants in a struct, to avoid clashes between libraries (note : why not using namespaces ??) | ||
- | ==== Item 40 : ==== | + | ===== Classes and Functions |
- | ==== Item 41 : ==== | ||
- | ==== Item 42 : ==== | ||
- | ==== Item 43 : ==== | ||
- | ==== Item 44 : ==== | + | === Item 29 : Avoid returning " |
+ | A handle is a pointer or a reference. If you do so, then it is possible to modify a const object using the handle returned by the const member function ! One way to go around it could be to return the result by value, or to return a handle on a copy of the data, but it costs time, and creating copies of objects lead to risks of memory leak. Another way is to return a const pointer or reference : it is both fast and safe, but it is not the same thing and may restrict callers unnecessarily (note: I don't agree with these drawbacks, the user can do a copy himself if he need to modify it). | ||
- | ==== Item 45 : ==== | + | === Item 30 : Avoid member functions that return pointers or references to members less accessible than themselves |
+ | Because if you do so, you change the access level of the returned member (so what was the matter to give it this access level). If you have no choice, try at least to return a const handle. | ||
- | ==== Item 46 : ==== | + | === Item 31 : Never return a reference to a local object or a dereferenced pointer initialized by new within the function |
+ | If you return a reference to a local object, as the object disappears when going out the function, this is automatic segmentation fault when using it. If you return a pointer initialized by a new within the function, you have to ask the user to delete it, but it is not reasonable, and sometimes impossible because of temporary objects (for example the result of operator + in '' | ||
- | ==== Item 47 : ==== | + | === Item 32 : Use enums for integral class constants |
+ | If you want to use different constants for classes, you can't use a global const variable or a ''# | ||
+ | <code cpp> | ||
+ | class X { | ||
+ | enum { BUFSIZE=100 }; | ||
+ | char buffer[BUFSIZE]; | ||
+ | } | ||
+ | </ | ||
- | ==== Item 48 : ==== | + | === Item 33 : Use inlining judiciously |
+ | Inlining avoid the cost of a function call, but it can increase dramatically the size of the code, what can be a problem on systems with limited memory, and can slow a lot the program by leading to pathological paging behavior on systems with virtual memory. | ||
- | ==== Item 49 : ==== | + | But there is more. The inline directive is just a request, and the compiler can decide to not inline the function (for example if it is recursive, or too long). When it is not inlined, as it must be defined in the header, the compiler will declare it static in order to avoid linking problems when several source files include the same header. Then there will be several copies of the code (and you still pay the cost of function calls). Moreover if you ask somewhere the address of the function, it will also create the body of the function. And debuggers cannot go through inline functions (note: with VisualStudio and g++, it is just disabled in debug mode ...). |
- | ==== Item 50 : ==== | + | A good methodology with inline functions is to inline only obvious functions at the beginning (getters& |
+ | |||
+ | === Item 34 : Minimize compilation dependencies between files === | ||
+ | C++ doesn' | ||
+ | |||
+ | There are two solutions for separating interface from implementation : | ||
+ | * Put all private stuff in another class (implementation class), and put a pointer to this class in the main class (interface class). The cost is one level of indirection for access to private (implementation) stuff. | ||
+ | * Make the interface class an abstract base class which contains public stuff (only pure virtual functions), and inheritate from it an implementation class. The cost is virtuality : one indirection for each function call, and more important you lose inlining (but this is also true for the previous solution). | ||
+ | But don't dismiss these methods because they have a cost, use them during development to minimize the impact on clients when implementation change, and replace interface and implementation classe with one concrete for production use if there is a real difference in speed or size. | ||
+ | |||
+ | ===== Inheritance and Object-Oriented design ===== | ||
+ | |||
+ | |||
+ | === Item 35 : Make sure public inheritance models " | ||
+ | |||
+ | === Item 36 : Differentiate between inheritance of interface and inheritance of implementation === | ||
+ | |||
+ | === Item 37 : Never redefine an inherited nonvirtual function === | ||
+ | |||
+ | === Item 38 : Never redefine an inherited default parameter value === | ||
+ | |||
+ | === Item 39 : Avoid casts down the inheritance hierarchy === | ||
+ | |||
+ | === Item 40 : Model " | ||
+ | |||
+ | === Item 41 : Use private inheritance judiciously === | ||
+ | |||
+ | === Item 42 : Differentiate between inheritance and templates === | ||
+ | |||
+ | === Item 43 : Use multiple inheritance judiciously === | ||
+ | |||
+ | === Item 44 : Say what you mean ; understand what you're saying === | ||
+ | |||
+ | ===== Miscellany ===== | ||
+ | |||
+ | |||
+ | === Item 45 : Know what functions C++ silently writes and calls === | ||
+ | |||
+ | === Item 46 : Prefer compile-time and link-time errors to runtime errors === | ||
+ | |||
+ | === Item 47 : Ensure that global objects are initialized before they' | ||
+ | |||
+ | === Item 48 : Pay attention to compile warnings === | ||
+ | |||
+ | === Item 49 : Plan for coming language features === | ||
+ | |||
+ | === Item 50 : Read the ARM === | ||
Line 262: | Line 411: | ||
===== Basics ===== | ===== Basics ===== | ||
- | ==== Item 1 : Distinguish between pointers and references | + | === Item 1 : Distinguish between pointers and references === |
References must refer to an object (no null reference), and that's why they must be initialized. Indeed you don't have to check if a reference parameter is null or not, contrary to pointers. But references can't be reassigned to refer to different objects. | References must refer to an object (no null reference), and that's why they must be initialized. Indeed you don't have to check if a reference parameter is null or not, contrary to pointers. But references can't be reassigned to refer to different objects. | ||
- | ==== Item 2 : Prefer C++-style casts ==== | + | === Item 2 : Prefer C++-style casts === |
Because they are easier to parse (both for humans and tools), and because it can avoid errors.\\ | Because they are easier to parse (both for humans and tools), and because it can avoid errors.\\ | ||
'' | '' | ||
'' | '' | ||
- | < | + | < |
void update(X *px); | void update(X *px); | ||
const X x; | const X x; | ||
Line 276: | Line 425: | ||
'' | '' | ||
'' | '' | ||
- | < | + | < |
typedef void (*FuncPtr)(); | typedef void (*FuncPtr)(); | ||
FuncPtr funcPtrArray[10]; | FuncPtr funcPtrArray[10]; | ||
Line 283: | Line 432: | ||
</ | </ | ||
- | ==== Item 3 : Never treat arrays polymorphically | + | === Item 3 : Never treat arrays polymorphically === |
That's to say don't place derived class objects in an array of base class pointers, if they don't have the same size, because indexing will use size of the base class. So problems when you write your loop, or when you delete the array (the compiler generates a loop, and it is said in the language specification that the result is undefined). | That's to say don't place derived class objects in an array of base class pointers, if they don't have the same size, because indexing will use size of the base class. So problems when you write your loop, or when you delete the array (the compiler generates a loop, and it is said in the language specification that the result is undefined). | ||
- | ==== Item 4 : ==== | + | === Item 4 : Avoid gratuitious default constructors |
===== Operators ===== | ===== Operators ===== | ||
- | ==== Item 5 : ==== | + | === Item 5 : Be wary of user-defined conversion functions |
- | ==== Item 6 : ==== | + | === Item 6 : Distinguish between prefix and postfix forms of increment and decrement operators |
- | ==== Item 7 : ==== | + | === Item 7 : Never overload &&, || or , === |
- | ==== Item 8 : ==== | + | === Item 8 : Understand the different meanings of new and delete |
===== Exceptions ===== | ===== Exceptions ===== | ||
- | ==== Item 9 : ==== | + | === Item 9 : Use destructors to prevent resource leaks === |
- | ==== Item 10 : ==== | + | === Item 10 : Prevent resource leaks in constructors |
- | ==== Item 11 : ==== | + | === Item 11 : Prevent exceptions from leaving destructors |
- | ==== Item 12 : ==== | + | === Item 12 : Understand how throwing and exception differs from passing a parameter or calling a virtual function |
- | ==== Item 13 : ==== | + | === Item 13 : Catch exceptions by reference |
- | ==== Item 14 : ==== | + | === Item 14 : Use exception specifications judiciously |
- | ==== Item 15 : ==== | + | === Item 15 : Understand the costs of exception handling |
===== Efficiency ===== | ===== Efficiency ===== | ||
- | ==== Item 16 : ==== | + | === Item 16 : Remember the 80-20 rule === |
- | ==== Item 17 : ==== | + | === Item 17 : Consider using lazy evaluation |
- | ==== Item 18 : ==== | + | === Item 18 : Amortize the cost of expected computations |
- | ==== Item 19 : ==== | + | === Item 19 : Understand the origin of temporary objects |
- | ==== Item 20 : ==== | + | === Item 20 : Facilitate the return value optimization |
- | ==== Item 21 : ==== | + | === Item 21 : Overload to avoid implicit type conversions |
- | ==== Item 22 : ==== | + | === Item 22 : Consider using op= instead of stand-alone op === |
- | ==== Item 23 : ==== | + | === Item 23 : Consider alternative libraries |
- | ==== Item 24 : Understand the costs of virtual functions, multiple inheritance, | + | === Item 24 : Understand the costs of virtual functions, multiple inheritance, |
Virtual functions works with virtual tables (one per class) containing pointers to functions, and virtual table pointers (one per object) pointing to the good virtual table. Hence it increases size of objects, per-class data, and reduce performance : because of indirections (but that's almost nothing), and mainly because it prevents inlining (except if the function is called from an object and not a pointer or a reference, but that's almost never the case). | Virtual functions works with virtual tables (one per class) containing pointers to functions, and virtual table pointers (one per object) pointing to the good virtual table. Hence it increases size of objects, per-class data, and reduce performance : because of indirections (but that's almost nothing), and mainly because it prevents inlining (except if the function is called from an object and not a pointer or a reference, but that's almost never the case). | ||
Line 343: | Line 492: | ||
===== Techniques ===== | ===== Techniques ===== | ||
+ | |||
+ | === Item 25 : Virtualizing constructors and non-member functions === | ||
+ | |||
+ | === Item 26 : Limiting the number of objects of a class === | ||
+ | |||
+ | === Item 27 : Requiring or prohibiting heap-based objects === | ||
+ | |||
+ | === Item 28 : Smart pointers === | ||
+ | |||
+ | === Item 29 : Reference counting === | ||
+ | |||
+ | === Item 30 : Proxy classes === | ||
+ | |||
+ | === Item 31 : Making functions virtual with respect to more than one object === | ||
+ | |||
+ | ===== Miscellany ===== | ||
+ | |||
+ | |||
+ | === Item 32 : Program in the future tense === | ||
+ | |||
+ | === Item 33 : Make non-leaf classes abstract === | ||
+ | |||
+ | === Item 34 : Understand how to combine C++ and C in the same program === | ||
+ | |||
+ | === Item 35 : Familiarize yourself with the language standard === | ||
+ | |||