Movatterモバイル変換


[0]ホーム

URL:




Chapter 8: Static Data And Functions

In the previous chapters we provided examples of classes where each objecthad its own set of data members data. Each of the class'smember functions could access any member of any object of its class.

In some situations it may be desirable to definecommon data fields, that may be accessed byall objects of theclass. For example, the name of the startup directory, used by a program thatrecursively scans the directory tree of a disk. A second example is a variablethat indicates whether some specific initialization has occurred. In that casethe object that was constructed first would perform the initialization andwould set the flag to `done'.

Such situations are also encountered inC, where several functions need toaccess the same variable. A common solution inC is to define all thesefunctions in one source file and to define the variablestatic: thevariable name is invisible outside the scope of the source file. This approachis quite valid, but violates our philosophy of using only one function persource file. AnotherC-solution is to give the variable in question anunusual name, e.g.,_6uldv8, hoping that other program parts won't usethis name by accident. Neither the first, nor the second legacyC solutionis elegant.

C++ solves the problem by definingstatic members: data and functions,common to all objects of a class and (when defined in the private section)inaccessible outside of the class. These static members are this chapter'stopic.

Static members cannot be defined as virtualfunctions. A virtual member function is an ordinary member in that it has athis pointer. As static member functions have nothis pointer, theycannot be declared virtual.

8.1: Static data

Any data member of a class can be declaredstatic; be it in thepublicorprivate section of the class interface. Such a data member is createdand initialized only once, in contrast to non-static data members which arecreated again and again for each object of the class.

Static data members are created as soon as theprogram starts. Even though they're created at the very beginning of aprogram's execution cycle they are nevertheless true members of their classes.

It is suggested to prefix the names of static member withs_ so they mayeasily be distinguished (in class member functions) from the class's datamembers (which should preferably start withd_).

Public static data members areglobal variables. They maybe accessed byall of the program's code, simply by using their classnames, the scope resolution operator and their member names. Example:

    class Test    {        static int s_private_int;        public:            static int s_public_int;    };    int main()    {        Test::s_public_int = 145;   // OK        Test::s_private_int = 12;   // wrong, don't touch                                    // the private parts    }

The example does not present an executable program. It merely illustratestheinterface, and not theimplementation ofstatic data members,which is discussed next.

8.1.1: Private static data

To illustrate the use of a static data member which is a privatevariable in a class, consider the following:
    class Directory    {        static char s_path[];        public:            // constructors, destructors, etc.    };

The data members_path[] is a private static data member. Duringthe program's execution onlyoneDirectory::s_path[] exists,even though multiple objects of the classDirectory may exist. Thisdata member could be inspected or altered by the constructor, destructor or byany other member function of the classDirectory.

Since constructors are called for each new object of a class, staticdata members are notinitialized byconstructors. At most they aremodified. The reason for this is thatstatic data members existbefore any constructor of the class has beencalled. Static data members are initialized when they are defined, outside ofany member function, exactly like the initialization of ordinary (non-class)global variables.

The definition and initialization of a static data member usually occursin one of the source files of the class functions, preferably in a source filededicated to the definition of static data members, calleddata.cc.

The data members_path[], used above, could thus bedefined and initialized as follows in a filedata.cc:

    #include "directory.ih"    char Directory::s_path[200] = "/usr/local";

In the class interface the static member is actually onlydeclared. Inits implementation (definition) its type and class name are explicitlymentioned. Note also that thesize specification can be left out of theinterface, as shown above. However, its sizeis (either explicitly orimplicitly) required when it is defined.

Note thatany source file could contain the definition of the staticdata members of a class. A separatedata.cc source file is advised, butthe source file containing, e.g.,main() could be used as well. Of course,any source file defining static data of a class must also include the headerfile of that class, in order for the static data member to be known to thecompiler.

A second example of a useful private static data member is given below. Assumethat a classGraphics defines the communication of a program with agraphics-capable device (e.g., a VGA screen). The initialization of thedevice, which in this case would be to switch from text mode to graphics mode,is an action of the constructor and depends on astatic flag variables_nobjects. The variables_nobjects simply counts the number ofGraphics objects which are present at one time. Similarly, the destructorof the class may switch back from graphics mode to text mode when the lastGraphics object ceases to exist. The class interface for thisGraphics class might be:

    class Graphics    {        static int s_nobjects;              // counts # of objects        public:            Graphics();            ~Graphics();                    // other members not shown.        private:            void setgraphicsmode();         // switch to graphics mode            void settextmode();             // switch to text-mode    };

The purpose of the variables_nobjects is to count the number ofobjects existing at a particular moment in time. When the first object iscreated, the graphics device is initialized. At the destruction of the lastGraphics object, the switch from graphics mode to text mode is made:

    int Graphics::s_nobjects = 0;           // the static data member    Graphics::Graphics()    {        if (!s_nobjects++)            setgraphicsmode();    }    Graphics::~Graphics()    {        if (!--s_nobjects)            settextmode();    }

Obviously, when the classGraphics would define more than oneconstructor, each constructor would need to increase the variables_nobjects and would possibly have to initialize the graphics mode.

8.1.2: Public static data

Data members could also be declared in thepublic section of a class. This,however, is deprecated (as it violates the principle ofdata hiding). Thestatic data members_path[] (cf. section8.1) could bedeclared in the public section of the class definition. This would allow allthe program's code to access this variable directly:
    int main()    {        getcwd(Directory::s_path, 199);    }

A declaration is not a definition. Consequently the variables_pathstill has to be defined. This implies that some source file still needs tocontains_path[] array's definition.

8.1.3: Initializing static const data

Staticconst data members should be initializedlike other static data members: in source files defining these datamembers.

Usually, if these data members are of integral or built-in primitive datatypes the compiler accepts in-class initializations of such datamembers. However, there is no formal rule requiring the compiler to doso. Compilations may or may not succeed depending on the optimizations used bythe compiler (e.g., using-O2 may result in a successful compilation, but-O0 (no-optimizations) may fail to compile, but then maybe only when sharedlibraries are used...).

In-class initializations of integer constant values (e.g., of typeschar,int, long, etc, maybeunsigned) is nevertheless possible using (e.g.,anonymous) enums. The following example illustrates how this can be done:

    class X    {        public:            enum         { s_x = 34 };            enum: size_t { s_maxWidth = 100 };    };

To avoid confusion caused by different compiler options static data membersshould always explicitly be defined and initialized in a (single) source file,whether or not they areconst data. Additionally, by defining them in asource file you avoid the inline-inconsistency.

8.1.4: Generalized constant expressions (constexpr)

InC macros are often used to let the preprocessor perform simplecalculations. Thesemacro functions may have arguments,as illustrated in the next example:
    #define xabs(x) ((x) < 0 ? -(x) : (x))

The disadvantages of macros are well known. The main reason for avoidingmacros is that they are not parsed by the compiler, but are processed by thepreprocessor resulting in mere text replacements and thus avoid type-safetyor syntactic checks of the macro definition by itself. Furthermore, sincemacros are processed by the preprocessor their use is unconditional, withoutacknowledging the context in which they are applied.NULL is an infamousexample. Ever tried to define anenum symbolNULL? orEOF? Chancesare that, if you did, the compiler threw strange error messages at you.

Generalized const expressions can be used as an alternative.

Generalized const expressions are recognized by the modifierconstexpr (akeyword), that is applied to the expression's type.

There is a small syntactic difference between the use of theconstmodifier and the use of theconstexpr modifier. While theconstmodifier can be applied to definitions and declarations alike, theconstexpr modifier can only be applied to definitions:

    extern int const externInt;     // OK: declaration of const int    extern int constexpr error;     // ERROR: not a definition

Variables defined with theconstexpr modifier have constant (immutable)values. But generalized const expressions are not just used to define constantvariables; they have other applications as well. Theconstexpr keyword isusually applied to functions, turning the function into aconstant-expression function.

A constant-expression function should not be confused with a functionreturning aconst value (although a constant-expression functiondoesreturn a (const) value). A constant expression function has thefollowing characteristics:

Such functions are also callednamed constant expressions with parameters.

These constant expression functions may or may not be called with argumentsthat have been evaluated at compile-time (not just `const arguments', as aconst parameter value is not evaluated at compile-time). If they arecalled with compile-time evaluated arguments then the returned value isconsidered aconst value as well.

This allows us to encapsulate expressions that can be evaluated at compile-timein functions, and it allows us to use these functions in situations wherepreviously the expressions themselves had to be used. The encapsulationreduces the number of occurrences of the expressions to one, simplifyingmaintenance and reduces the probability of errors.

If arguments that could not be compile-time evaluated are passed toconstant-expression functions, then these functions act like any otherfunction, in that their return values are no longer considered constantexpressions.

Assume some two-dimensional arrays must be converted to one-dimensionalarrays. The one-dimensional array must havenrows * ncols + nrows +ncols + 1 elements, to store row, column, and total marginals, as well as theelements of the source array itself. Furthermore assume thatnrows andncols have been defined as globally availablesize_t const values(they could be a class's static data). The one-dimensional arrays are datamembers of a class or struct, or they are also defined as global arrays.

Now that constant-expression functions are available the expression returningthe number of the required elements can be encapsulated in such a function:

    size_t const nRows = 45;    size_t const nCols = 10;    size_t constexpr nElements(size_t rows, size_t cols)    {        return rows * cols + rows + cols + 1;    }        ....    int intLinear[ nElements(nRows, nCols) ];    struct Linear    {        double d_linear[ nElements(nRows, nCols) ];    };

If another part of the program needs to use a linear array for an array ofdifferent sizes then the constant-expression function can also be used. E.g.,

    string stringLinear[ nElements(10, 4) ];

Constant-expression functions can be used in other constant expressionfunctions as well. The following constant-expression function returns half thevalue, rounded upwards, that is returned bynElements:

    size_t constexpr halfNElements(size_t rows, size_t cols)    {        return (nElements(rows, cols) + 1) >> 1;    }

Classes should not expose their data members to external software, so asto reduce coupling between classes and external software. But if a classdefines astatic const size_t data member then that member's value couldvery well be used to define entities living outside of the class's scope, likethe number of elements of an array or to define the value of some enum. Insituations like these constant-expression functions are the perfect tool tomaintain proper data hiding:

    class Data    {        static size_t const s_size = 7;        public:            static size_t constexpr size();            size_t constexpr mSize();    };    size_t constexpr Data::size()    {        return s_size;    }    size_t constexpr Data::mSize()    {        return size();    }    double data[ Data::size() ];        // OK: 7 elements    short data2[ Data().mSize() ];      // also OK: see below

Please note the following:

Some final notes:constexpr functions may

8.1.4.1: Constant expression data

As we've seen, (member) functions and variables of primitive data types can bedefined using theconstexpr modifier. What about class-type objects?

Objects of classes are values of class type, and like values of primitivetypes they can be defined with theconstexpr specifier. Constantexpression class-type objects must be initialized with constant expressionarguments; the constructor that is actually used must itself have beendeclared with theconstexpr modifier. Note again that theconstexprconstructor's definition must have been seen by the compiler before theconstexpr object can be constructed:

    class ConstExpr    {        public:            constexpr ConstExpr(int x);    };    ConstExpr ok{ 7 };              // OK: not declared as constexpr    constexpr ConstExpr err{ 7 };   // ERROR: constructor's definition                                    //        not yet seen    constexpr ConstExpr::ConstExpr(int x)    {}    constexpr ConstExpr ok{ 7 };                // OK: definition seen    constexpr ConstExpr okToo = ConstExpr{ 7 }; // also OK

Aconstant-expression constructor has the following characteristics:

An object constructed with a constant-expression constructor is called auser-defined literal. Destructors and copy constructors of user-defined literals must betrivial.

Theconstexpr characteristic of user-defined literals may or may notbe maintained by its class's members. If a member is not declared with aconstexpr return value, then using that member does not result in aconstant-expression. If a memberdoes declare aconstexpr return valuethen that member's return value is considered aconstexpr if it is byitself a constant expression function. To maintain itsconstexprcharacteristics it can refer to its classes' data membersonly if itsobject has been defined with theconstexpr modifier, as illustrated by theexample:

    class Data    {        int d_x;        public:            constexpr Data(int x)            :                d_x(x)            {}            int constexpr cMember()            {                return d_x;            }            int member() const            {                return d_x;            }    };    Data d1{ 0 };           // OK, but not a constant expression    enum e1 {        ERR = d1.cMember()  // ERROR: cMember(): no constant    };                      //        expression anymore    constexpr Data d2{ 0 }; // OK, constant expression    enum e2 {        OK = d2.cMember(),  // OK: cMember(): now a constant                            //                expression        ERR = d2.member(),  // ERR: member(): not a constant    };                      //                expression

8.2: Static member functions

In addition to static data members,C++ allows us to definestatic member functions. Similar to static data that are shared byall objects of the class, static member functions also exist without anyassociated object of their class.

Static member functions can access all staticmembers of their class, butalso the members (private orpublic)of objects of their classif they are informed about the existence ofthese objects (as in the upcoming example). As static member functions are notassociated with any object of their class they do not have athis pointer. In fact, a static member function is completely comparableto aglobal function, not associated with any class (i.e., in practice theyare. See the next section (8.2.1) for a subtle note). Sincestatic member functions do not require an associated object, static memberfunctions declared in the public section of a class interface may be calledwithout specifying an object of its class. The following example illustratesthis characteristic of static member functions:

    class Directory    {        string d_currentPath;        static char s_path[];        public:            static void setpath(char const *newpath);            static void preset(Directory &dir, char const *newpath);    };    inline void Directory::preset(Directory &dir, char const *newpath)    {                                                    // see the text below        dir.d_currentPath = newpath;                // 1    }    char Directory::s_path[200] = "/usr/local";     // 2    void Directory::setpath(char const *newpath)    {        if (strlen(newpath) >= 200)            throw "newpath too long";        strcpy(s_path, newpath);                    // 3    }    int main()    {        Directory dir;        Directory::setpath("/etc");                 // 4        dir.setpath("/etc");                        // 5        Directory::preset(dir, "/usr/local/bin");   // 6        dir.preset(dir, "/usr/local/bin");          // 7    }

In the example only public static member functions were used.C++also allows the definition of private static member functions. Such functionscan only be called by member functions of their class.

8.2.1: Calling conventions

As noted in the previous section, static (public) member functions arecomparable to classless functions. However, formally this statement is nottrue, as theC++ standard does not prescribe the same calling conventionsfor static member functions as for classless global functions.

In practice the calling conventions are identical, implying that theaddress of a static member function could be used as an argument of functionshaving parameters that are pointers to (global) functions.

If unpleasant surprises must be avoided at all cost, it is suggested tocreate global classlesswrapper functions around static member functionsthat must be used ascall back functions for other functions.

Recognizing that the traditional situations in which call back functionsare used inC are tackled inC++ using template algorithms(cf. chapter19), let's assume that we have a classPersonhaving data members representing the person's name, address, phone andmass. Furthermore, assume we want to sort an array of pointers toPersonobjects, by comparing thePerson objects these pointers point to. Keepingthings simple, we assume that the following public static member exists:

    int Person::compare(Person const *const *p1, Person const *const *p2);

A useful characteristic of this member is that it may directly inspect therequired data members of the twoPerson objects passed to the memberfunction using pointers to pointers (double pointers).

Most compilers allow us to pass this function's address as the address ofthe comparison function for the standardCqsort() function. E.g.,

    qsort    (        personArray, nPersons, sizeof(Person *),        reinterpret_cast<int(*)(void const *, void const *)>(Person::compare)    );

However, if the compiler uses different calling conventions for staticmembers and for classless functions, this might not work. In such a case, aclassless wrapper function like the following may be used profitably:

    int compareWrapper(void const *p1, void const *p2)    {        return            Person::compare            (                static_cast<Person const *const *>(p1),                static_cast<Person const *const *>(p2)            );    }

resulting in the following call of theqsort() function:

    qsort(personArray, nPersons, sizeof(Person *), compareWrapper);

Note:




[8]ページ先頭

©2009-2025 Movatter.jp