Flylib.com

Books Software

 
 
 

FAQ 16.04 Can inline functions safely access static data members?

FAQ 16.04 Can inline functions safely access static data members ?

No!

Static data members should normally be accessed by non- inline functions only (and then only from non- inline functions that are defined in the same source file as the static data member's definition). In some cases inline functions can access static member data, but the programmer needs to think through the issues fairly carefully —they're somewhat tricky.

Suppose class Fred contains two static data members: Fred::i_ is of type int and Fred::s_ is of class string , the standard string class. The data member Fred::i_ is initialized before any code starts running, so Fred::i_ can be accessed from an inline function. However if an inline function accesses Fred::s_ , and if the inline function is called from another compilation unit, the inline function might access Fred::s_ before it has been initialized (that is, before the constructor of Fred::s_ has run). This would be a disaster.

Static data members are guaranteed to be initialized before the first call to any non- inline function within the same source file as the static data's definition. In the following example, file Fred.cpp defines both static data member Fred::s_ and member function Fred::f() . This means that Fred::s_ will be initialized before Fred::f() is called. But if someone calls inline function Fred::g() before calling Fred::f() , accessing Fred::s_ could be a disaster since Fred::s_ might not be initialized yet.

Here is file Fred.hpp .

#include <string>
#include <iostream>
using namespace std;

class Fred {
public:
  static void f() throw();
  static string g() throw();
private:
  static string s_;
};

inline string Fred::g() throw()
{ cout << s_; return s_; }

<-- 1

(1) EVIL: Fred::s_ might not be initialized yet

Here is file Fred.cpp .

#include "Fred.hpp"

string Fred::s_ = "Hello";

void Fred::f() throw()
{ cout << s_; }

<-- 1

(1) GOOD: Fred::s_ is guaranteed to be initialized

Here is an example showing how the above code could possibly fail (this code is assumed to be in a different source file, such as main.cpp ).

#include "Fred.hpp"

int main()
{ Fred::g(); }

Note that some—but not all—compilers will initialize static data member Fred::s_ before main() begins. Thus this code is doubly evil since it will subtly fail on some compilers and accidentally work on others. In fact, its success or failure might even depend on the order that object files are passed to the linker, and some visual environments hide the linker so well that many programmers don't even know the order in which object files are passed to the linker.

To make matters worse , the following source file, say even-worse.cpp , calls inline function Fred::g() —therefore accessing Fred::s_ —during static initialization. Many compilers will cause this to happen before main() begins, so this is even more likely to cause a problem (but again, the problem will depend randomly on things like the link order, the compiler, the version number, the phase of the moon, etc.).

#include "Fred.hpp"

string x = Fred::g();

FAQ 16.05 What is an analogy for static member functions?

Static member functions are like services attached to the factory rather than services attached to the objects produced by the factory.

#include <cstdlib>
#include <iostream>
using namespace std;

class Car {
public:

  Car() throw();
  Car(const Car& c) throw();
  // No need for an explicit assignment operator or destructor
  // since these don't create new Car objects.

  static int num() throw();

<-- 1

int odometer() const throw();

<-- 2

void drive() throw();

<-- 3

private:
  static int num_;

<-- 4

int        miles_;

<-- 5

};

Car::Car() throw()
: miles_(0)
{
  cout << "Car ctor\n";
  ++num_;
}

Car::Car(const Car& c) throw()
: miles_(c.miles_)
{
  cout << "Car copy\n";
  ++num_;
}

int Car::num() throw()

<-- 6

{ return num_; }

int Car::odometer() const throw()
{ return miles_; }
void Car::drive() throw()
{ ++miles_; }

int Car::num_ = 0;

<-- 7

(1) Class service

(2) Object service

(3) Object service: You drive a Car, not a factory

(4) Class data

(5) Object data

(6) Should be in same source file as num_; see FAQ 16.04

(7) Class data is automatically initialized , often before main()

Some services make sense only when applied to an object.

void fiddleWithObject(Car& car) throw()
{
  while (rand() % 10 != 0)
    car.drive();
  cout << "car.odometer() = " << car.odometer() << '\n';
}

Some services make sense only when applied to the factory.

void fiddleWithClass() throw()
{
  cout << "Car::num() returns " << Car::num() << '\n';

  #ifdef GENERATE_ERROR
    Car::drive();

<-- 1

Car::odometer();

<-- 2

#endif
}

(1) ERROR: Can't drive a factory

(2) ERROR: Factories don't have odometers

Since the factory exists before it produces its first object, the factory can provide services before instantiating an object. That is, fiddleWithClass() can be called before the first Car object is created and/or after the last Car object is destructed:

int main()
{
  fiddleWithClass();

  {
    Car a;
    fiddleWithClass();
    fiddleWithObject(a);
  }

  fiddleWithClass();
}