Выбрать главу

In Volume 1, you were shown how to control initialization order by defining a static object inside a function. This delays the initialization of the object until the first time the function is called. If the function returns a reference to the static object, it gives you the effect of a Singleton while removing much of the worry of static initialization. For example, suppose you want to create a log file upon the first call to a function that returns a reference to that log file. This header file will do the trick:

//: C10:LogFile.h

#ifndef LOGFILE_H

#define LOGFILE_H

#include <fstream>

std::ofstream& logfile();

#endif // LOGFILE_H ///:~

The implementation must not be inlined, because that would mean that the whole function, including the static object definition within, could be duplicated in any translation unit where it’s included, which violates C++’s one-definition rule.[117] This would most certainly foil the attempts to control the order of initialization (but potentially in a subtle and hard-to-detect fashion). So the implementation must be separate:

//: C10:LogFile.cpp {O}

#include "LogFile.h"

std::ofstream& logfile() {

  static std::ofstream log("Logfile.log");

  return log;

} ///:~

Now the log object will not be initialized until the first time logfile( ) is called. So if you create a function:

//: C10:UseLog1.h

#ifndef USELOG1_H

#define USELOG1_H

void f();

#endif // USELOG1_H ///:~

that uses logfile() in its implementation:

//: C10:UseLog1.cpp {O}

#include "UseLog1.h"

#include "LogFile.h"

void f() {

  logfile() << __FILE__ << std::endl;

} ///:~

And you use logfile() again in another file:

//: C10:UseLog2.cpp

//{L} LogFile UseLog1

#include "UseLog1.h"

#include "LogFile.h"

using namespace std;

void g() {

  logfile() << __FILE__ << endl;

}

int main() {

  f();

  g();

} ///:~

the log object doesn’t get created until the first call to f( ).

You can easily combine the creation of the static object inside a member function with the Singleton class. SingletonPattern.cpp can be modified to use this approach:[118] 

//: C10:SingletonPattern2.cpp

// Meyers’ Singleton

#include <iostream>

using namespace std;

class Singleton {

  int i;

  Singleton(int x) : i(x) { }

  void operator=(Singleton&);

  Singleton(const Singleton&);

public:

  static Singleton& instance() {

    static Singleton s(47);

    return s;

  }

  int getValue() { return i; }

  void setValue(int x) { i = x; }

};

int main() {

  Singleton& s = Singleton::instance();

  cout << s.getValue() << endl;

  Singleton& s2 = Singleton::instance();

  s2.setValue(9);

  cout << s.getValue() << endl;

} ///:~

An especially interesting case occurs if two Singletons depend on each other, like this:

//: C10:FunctionStaticSingleton.cpp

class Singleton1 {

  Singleton1() {}

public:

  static Singleton1& ref() {

    static Singleton1 single;

    return single;

  }

};

class Singleton2 {

  Singleton1& s1;

  Singleton2(Singleton1& s) : s1(s) {}

public:

  static Singleton2& ref() {

    static Singleton2 single(Singleton1::ref());

    return single;

  }

  Singleton1& f() { return s1; }

};

int main() {

  Singleton1& s1 = Singleton2::ref().f();

} ///:~

When Singleton2::ref( ) is called, it causes its sole Singleton2 object to be created. In the process of this creation, Singleton1::ref( ) is called, and that causes the sole Singleton1 object to be created. Because this technique doesn’t rely on the order of linking or loading, the programmer has much better control over initialization, leading to fewer problems.

Yet another variation on Singleton allows you to separate the "Singleton-ness" of an object from its implementation. This is achieved through templates, using the Curiously Recurring Template Pattern mentioned in Chapter 5.

//: C10:CuriousSingleton.cpp

// Separates a class from its Singleton-ness (almost)

#include <iostream>

using namespace std;

template<class T>

class Singleton {

public:

  static T& instance() {

    static T theInstance;

    return theInstance;

  }

protected:

  Singleton() {}

  virtual ~Singleton() {}

private:

  Singleton(const Singleton&);

  Singleton& operator=(const Singleton&);

};

// A sample class to be made into a Singleton

class MyClass : public Singleton<MyClass> {

  int x;

protected:

  friend class Singleton<MyClass>;

  MyClass(){x = 0;}

public:

  void setValue(int n) { x = n; }

  int getValue() const { return x; }

};

int main() {

  MyClass& m = MyClass::instance();

  cout << m.getValue() << endl;

  m.setValue(1);

  cout << m.getValue() << endl;

} ///:~

MyClass is made a Singleton by:

1.       Making its constructor private or protected.

2.      Making Singleton<MyClass> a friend.

3.      Deriving MyClass from Singleton<MyClass>.

The self-referencing in step 3 may sound implausible, but as we explained in Chapter 5, it works because there is only a static data dependency on the template argument in the Singleton template. In other words, the code for the class Singleton<MyClass> can be instantiated by the compiler because it is not dependent on the size of MyClass. It’s only later, when Singleton<MyClass>::instance( ) is first called, that the size of MyClass is needed, and of course by then compilation of MyClass is complete and its size is known.[119] 

вернуться

117

The C++ standards states: “No translation unit shall contain more than one definition of any variable, function, class type, enumeration type or template… Every program shall contain exactly one definition of every non-inline function or object that is used in that program.”

вернуться

118

This is known as Meyers’ Singleton, after its creator, Scott Meyers.

вернуться

119

Andrei Alexandrescu develops a superior, policy-based solution to implementing the Singleton pattern in Modern C++ Design.