B. Stroustrup - The C++ Programming Language (794319), страница 99
Текст из файла (страница 99)
For example:Date::Date(int dd):d{dd}{// check that the Date is valid}This is equivalent to:Date::Date(int dd):d{dd}, m{today.m}, y{today.y}{// check that the Date is valid}16.2.8 In-Class Function DefinitionsA member function defined within the class definition – rather than simply declared there – is takento be an inline (§12.1.5) member function. That is, in-class definition of member functions is forsmall, rarely modified, frequently used functions. Like the class definition it is part of, a memberfunction defined in-class can be replicated in several translation units using #include. Like the classitself, the member function’s meaning must be the same wherever it is #included (§15.2.3).A member can refer to another member of its class independently of where that member isdefined (§6.3.4).
Consider:class Date {public:void add_month(int n) { m+=n; }// ...private:int d, m, y;};// increment the Date’s mThat is, function and data member declarations are order independent. I could equivalently havewritten:Section 16.2.8In-Class Function Definitionsclass Date {public:void add_month(int n) { m+=n; }// ...private:int d, m, y;};461// increment the Date’s minline void Date::add_month(int n) // add n months{m+=n;// increment the Date’s m}This latter style is often used to keep class definitions simple and easy to read.
It also provides atextual separation of a class’s interface and implementation.Obviously, I simplified the definition of Date::add_month; just adding n and hoping to hit a gooddate is too naive (§16.3.1).16.2.9 MutabilityWe can define a named object as a constant or as a variable.
In other words, a name can refer to anobject that holds an immutable or a mutable value. Since the precise terminology can be a bitclumsy, we end up referring to some variables as being constant or briefer still to const variables.However odd that may sound to a native English speaker, the concept is useful and deeply embedded in the C++ type system. Systematic use of immutable objects leads to more comprehensiblecode, to more errors being found early, and sometimes to improved performance. In particular,immutability is a most useful property in a multi-threaded program (§5.3, Chapter 41).To be useful beyond the definition of simple constants of built-in types, we must be able todefine functions that operate on const objects of user-defined types.
For freestanding functions thatmeans functions that take const T& arguments. For classes it means that we must be able to definemember functions that work on const objects.16.2.9.1 Constant Member FunctionsThe Date as defined so far provides member functions for giving a Date a value. Unfortunately, wedidn’t provide a way of examining the value of a Date. This problem can easily be remedied byadding functions for reading the day, month, and year:class Date {int d, m, y;public:int day() const { return d; }int month() const { return m; }int year() const;void add_year(int n);// ...};// add n years462ClassesChapter 16The const after the (empty) argument list in the function declarations indicates that these functionsdo not modify the state of a Date.Naturally, the compiler will catch accidental attempts to violate this promise. For example:int Date::year() const{return ++y;// error: attempt to change member value in const function}When a const member function is defined outside its class, the const suffix is required:int Date::year(){return y;}// error: const missing in member function typeIn other words, const is part of the type of Date::day(), Date::month(), and Date::year().A const member function can be invoked for both const and non-const objects, whereas a nonconst member function can be invoked only for non-const objects.
For example:void f(Date& d, const Date& cd){int i = d.year();// OKd.add_year(1);// OKint j = cd.year();cd.add_year(1);// OK// error: cannot change value of a const Date}16.2.9.2 Physical and Logical ConstnessOccasionally, a member function is logically const, but it still needs to change the value of a member. That is, to a user, the function appears not to change the state of its object, but some detail thatthe user cannot directly observe is updated. This is often called logical constness. For example,the Date class might have a function returning a string representation. Constructing this representation could be a relatively expensive operation.
Therefore, it would make sense to keep a copy sothat repeated requests would simply return the copy, unless the Date’s value had been changed.Caching values like that is more common for more complicated data structures, but let’s see how itcan be achieved for a Date:class Date {public:// ...string string_rep() const;// string representationprivate:bool cache_valid;string cache;void compute_cache_value(); // fill cache// ...};Section 16.2.9.2Physical and Logical Constness463From a user’s point of view, string_rep doesn’t change the state of its Date, so it clearly should be aconst member function. On the other hand, the cache and cache_valid members must change occasionally for the design to make sense.Such problems could be solved through brute force using a cast, for example, a const_cast(§11.5.2).
However, there are also reasonably elegant solutions that do not involve messing withtype rules.16.2.9.3 mutableWe can define a member of a class to beobject:mutable,class Date {public:// ...string string_rep() const;private:mutable bool cache_valid;mutable string cache;void compute_cache_value() const;// ...};meaning that it can be modified even in aconst// string representation// fill (mutable) cacheNow we can define string_rep() in the obvious way:string Date::string_rep() const{if (!cache_valid) {compute_cache_value();cache_valid = true;}return cache;}We can now use string_rep() for both const and non-const objects. For example:void f(Date d, const Date cd){string s1 = d.string_rep();string s2 = cd.string_rep();// ...}// OK!16.2.9.4 Mutability through IndirectionDeclaring a member mutable is most appropriate when only a small part of a representation of asmall object is allowed to change.
More complicated cases are often better handled by placing thechanging data in a separate object and accessing it indirectly. If that technique is used, the stringwith-cache example becomes:464ClassesChapter 16struct cache {bool valid;string rep;};class Date {public:// ...string string_rep() const;private:cache∗ c;void compute_cache_value() const;// ...};// string representation// initialize in constructor// fill what cache refers tostring Date::string_rep() const{if (!c−>valid) {compute_cache_value();c−>valid = true;}return c−>rep;}The programming techniques that support a cache generalize to various forms of lazy evaluation.Note that const does not apply (transitively) to objects accessed through pointers or references.The human reader may consider such an object as ‘‘a kind of subobject,’’ but the compiler does notknow such pointers or references to be any different from any others.
That is, a member pointerdoes not have any special semantics that distinguish it from other pointers.16.2.10 Self-ReferenceThe state update functions add_year(), add_month(), and add_day() (§16.2.3) were defined not toreturn values. For such a set of related update functions, it is often useful to return a reference tothe updated object so that the operations can be chained. For example, we would like to write:void f(Date& d){// ...d.add_day(1).add_month(1).add_year(1);// ...}to add a day, a month, and a year to d. To do this, each function must be declared to return a reference to a Date:class Date {// ...Section 16.2.10Self-Reference465Date& add_year(int n); // add n yearsDate& add_month(int n); // add n monthsDate& add_day(int n);// add n days};Each (non-static) member function knows for which object it was invoked and can explicitly referto it.
For example:Date& Date::add_year(int n){if (d==29 && m==2 && !leapyear(y+n)) { // beware of February 29d = 1;m = 3;}y += n;return ∗this;}The expression ∗this refers to the object for which a member function is invoked.In a non-static member function, the keyword this is a pointer to the object for which the function was invoked. In a non-const member function of class X, the type of this is X∗. However, thisis considered an rvalue, so it is not possible to take the address of this or to assign to this. In aconst member function of class X, the type of this is const X∗ to prevent modification of the objectitself (see also §7.5).Most uses of this are implicit.
In particular, every reference to a non-static member from withina class relies on an implicit use of this to get the member of the appropriate object. For example,the add_year function could equivalently, but tediously, have been defined like this:Date& Date::add_year(int n){if (this−>d==29 && this−>m==2 && !leapyear(this−>y+n)) {this−>d = 1;this−>m = 3;}this−>y += n;return ∗this;}One common explicit use of this is in linked-list manipulation.