如何处理单例

How to handle a singleton?

本文关键字:单例 处理 何处理      更新时间:2023-10-16

我正在扩展一些最初不是我写的代码,并且已经使用了singleton(尽管从我所读到的内容来看,它可能没有完全正确实现)。无论如何,我不认为单例本身真的应该改变。这是我得到的:

class WindowManager
{
    private:
        static WindowManager*           s_wndmgr; //A singleton maintains a pointer to itself as a class variable
        //To ensure the integrity of a singleton
        //Constructors and the destructor should remain private
        WindowManager();
        ~WindowManager();
    public:
        static void Create();
        static void Destroy();
        static inline WindowManager* Get()
        {
            return s_wndmgr;
        }
        void Render();
}

WindowManager* WindowManager::s_wndmgr = NULL;
WindowManager::WindowManager()
{
    s_wndmgr = NULL;
}
WindowManager::~WindowManager()
{
    //Cleanup other stuff if necessary
    delete s_wndmgr;
}
void WindowManager::Create()
{
    if ( !s_wndmgr ) s_wndmgr = new WindowManager();
}
void WindowManager::Destroy()
{
    if ( s_wndmgr ) delete s_wndmgr;
}

我以前从未接触过单态,而且我对C++本身还相当陌生。对我来说,我习惯于实例化调用构造函数的类,但在这种情况下,我可以看到Create函数处理了这一点,但这一切如何与从另一个类调用Create,然后使用Get返回允许我调用类似Render的成员函数的实例相联系?

我知道这远非正确,但我想做的是:

class myotherclass
{
    private:
        WindowManager*  m_wmgr;         //Window manager
}
 WindowManager::Create();  //This line being the real issue here
 m_wnmgr = WindowManager::Get();
 m_wnmgr->Render();

调用者不需要调用Create()。它可能隐藏在Get()内部。如果需要创建singleton实例,它会的。这称为惰性初始化。

static inline WindowManager* Get()
{
    Create();
    return s_wndmgr;
}

您可以完全删除Create()方法,并将其移动到Get():中

static inline WindowManager* Get()
{
    if ( !s_wndmgr ) s_wndmgr = new WindowManager();
    return s_wndmgr;
}

或者更好的是,去掉私有指针,返回一个引用而不是指针:

static inline WindowManager& Get()
{
    static WindowManager instance;
    return instance;
}

然后,就这样使用它:

m_wnmgr = WindowManager::Get();
m_wnmgr.Render();

调用方必须像您发布的代码中那样调用Create(),这确实没有问题,除非忘记了这一点,并且返回并使用了未初始化的指针。保护自己不让自己有机会忘记这样的事情是件好事。

我有一个相当大的项目,我有几个Singleton,所以这里所做的是我有了一个Singleton类对象,它是所有Singleton的基类。它有一个受保护的构造函数,所以你不能直接创建Singleton对象,但任何从它派生的类,即Singleton类型对象,都可以。这是我的Singleton类的声明和定义。

Singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H
class Singleton {
public:
    enum SingletonType {
        TYPE_LOGGER = 0, // Must Be First!
        TYPE_SETTINGS,
        TYPE_ENGINE,
        TYPE_ANIMATION_MANAGER,
        TYPE_SHADER_MANAGER,
        TYPE_ASSET_STORAGE,
        TYPE_AUDIO_MANAGER,
        TYPE_FONT_MANAGER,
        TYPE_BATCH_MANAGER,     
    }; // Type
private:
    SingletonType m_eType;
public:
    virtual ~Singleton();
protected:
    explicit Singleton( SingletonType eType );
    void    logMemoryAllocation( bool isAllocated ) const;
private:
    Singleton( const Singleton& c ); // Not Implemented
    Singleton& operator=( const Singleton& c ); // Not Implemented
}; // Singleton
#endif // SINGLETON_H

Singleton.cpp

#include "stdafx.h"
#include "Logger.h"
#include "Singleton.h"
#include "Settings.h"
struct SingletonInfo {
    const std::string strSingletonName;
    bool              isConstructed;
    SingletonInfo( const std::string& strSingletonNameIn ) :
        strSingletonName( strSingletonNameIn ),
        isConstructed( false )
    {}
}; // SingletonInfo
// Order Must Match Types Defined In Singleton::SingletonType enum
static std::array<SingletonInfo, 9> s_aSingletons = { SingletonInfo( "Logger" ),
                                                      SingletonInfo( "Settings" ),
                                                      SingletonInfo( "Engine" ),
                                                      SingletonInfo( "AnimationManager" ),
                                                      SingletonInfo( "ShaderManager" ),
                                                      SingletonInfo( "AssetStorage" ),
                                                      SingletonInfo( "AudioManager" ),
                                                      SingletonInfo( "FontManager" ),
                                                      SingletonInfo( "BatchManager" ) };
// ----------------------------------------------------------------------------
// Singleton()
Singleton::Singleton( SingletonType eType ) :
m_eType( eType ) {
    bool bSaveInLog = s_aSingletons.at( TYPE_LOGGER ).isConstructed;
    try {
        if ( !s_aSingletons.at( eType ).isConstructed ) {
            // Test Initialization Order
            for ( int i = 0; i < eType; ++i ) {
                if ( !s_aSingletons.at( i ).isConstructed ) {
                    throw ExceptionHandler( s_aSingletons.at( i ).strSingletonName + " must be constructed before constructing " + s_aSingletons.at( eType ).strSingletonName, bSaveInLog  );
                }
            }
            s_aSingletons.at( eType ).isConstructed = true;
            if ( s_aSingletons.at( TYPE_ENGINE ).isConstructed &&
                Settings::get()->isDebugLoggingEnabled( Settings::DEBUG_MEMORY ) ) {
                logMemoryAllocation( true );
            }
        } else {
            throw ExceptionHandler( s_aSingletons.at( eType ).strSingletonName + " can only be constructed once.", bSaveInLog );
        }
    } catch ( std::exception& ) {
        // eType Is Out Of Range
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Invalid Singleton Type Specified: " << eType;
        throw ExceptionHandler( strStream, bSaveInLog );
    }
} // Singleton    
// ----------------------------------------------------------------------------
// ~Singleton()
Singleton::~Singleton() {
    if ( s_aSingletons.at( TYPE_ENGINE ).isConstructed &&
        Settings::get()->isDebugLoggingEnabled( Settings::DEBUG_MEMORY ) ) {
        logMemoryAllocation( false );
    }
    s_aSingletons.at( m_eType ).isConstructed = false;
} // ~Singleton
// ----------------------------------------------------------------------------
// logMemoryAllocation()
void Singleton::logMemoryAllocation( bool isAllocated ) const {
    if ( isAllocated ) {
        Logger::log( "Created " + s_aSingletons.at( m_eType ).strSingletonName );
    } else {
        Logger::log( "Destroyed " + s_aSingletons.at( m_eType ).strSingletonName );
    }
} // logMemoryAllocation

我将展示两个派生类的外观;Logger&设置类

Logger.h

#ifndef LOGGER_H
#define LOGGER_H
#include "Singleton.h"
class Logger sealed : public Singleton {
public:
    // Number Of Items In Enum Type Must Match The Number Of Items
    // And Order Of Items Stored In s_aLogTypes
    enum LoggerType {
        TYPE_INFO = 0,
        TYPE_WARNING,
        TYPE_ERROR,
        TYPE_CONSOLE,
    }; // Type
private:
    std::string m_strLogFilename;
    unsigned    m_uMaxCharacterLength;
    std::array<std::string, 4>  m_aLogTypes;
    const std::string           m_strUnknownLogType;
    HANDLE m_hConsoleOutput;
    WORD   m_consoleDefaultColor;
public:
    explicit Logger( const std::string& strLogFilename );
    virtual ~Logger();
    static void log( const std::string& strText, LoggerType eLogType = TYPE_INFO );
    static void log( const std::ostringstream& strStreamText, LoggerType eLogType = TYPE_INFO );
    static void log( const char* szText,  LoggerType eLogType = TYPE_INFO );
private:
    Logger( const Logger& c ); // Not Implemented
    Logger& operator=( const Logger&  c ); // Not Implemented
}; // Logger
#endif // LOGGER_H

Logger.cpp

#include "stdafx.h"
#include "Logger.h"
#include "BlockThread.h"
#include "TextFileWriter.h"
static Logger* s_pLogger = nullptr;
static CRITICAL_SECTION s_criticalSection;
static const WORD WHITE_ON_RED = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED; // White Text On Red Background
// ----------------------------------------------------------------------------
// Logger()
// Initialize A File To Be Used For Logging
Logger::Logger( const std::string& strLogFilename ) :
Singleton( TYPE_LOGGER ),
m_strLogFilename( strLogFilename ),
m_uMaxCharacterLength( 0 ),
m_strUnknownLogType( "UNKNOWN" ) {
    // Oder Must Match Types Defined In Logger::Type enum
    m_aLogTypes[0] = "Info";
    m_aLogTypes[1] = "Warning";
    m_aLogTypes[2] = "Error";
    m_aLogTypes[3] = ""; // Console
    // Find Widest Log Type String
    m_uMaxCharacterLength = m_strUnknownLogType.size();
    for each( const std::string& strLogType in m_aLogTypes ) {
        if ( m_uMaxCharacterLength < strLogType.size() ) {
             m_uMaxCharacterLength = strLogType.size();
        }
    }
    InitializeCriticalSection( &s_criticalSection );
    BlockThread blockTread( s_criticalSection ); // Enter Critical Section
    // Start Log File
    TextFileWriter file( m_strLogFilename, false, false );
    // Prepare Console
    m_hConsoleOutput = GetStdHandle( STD_OUTPUT_HANDLE );
    CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
    GetConsoleScreenBufferInfo( m_hConsoleOutput, &consoleInfo );
    m_consoleDefaultColor = consoleInfo.wAttributes;
    s_pLogger = this;
    logMemoryAllocation( true );
} // Logger
// ----------------------------------------------------------------------------
// ~Logger()
Logger::~Logger() {
    logMemoryAllocation( false );
    s_pLogger = nullptr;
    DeleteCriticalSection( &s_criticalSection );
} // ~Logger
// ----------------------------------------------------------------------------
// log( const std::string )
void Logger::log( const std::string& strText, LoggerType eLogType ) {
    log( strText.c_str(), eLogType );
} // log( const std::string )
// ----------------------------------------------------------------------------
// log( const std::ostringstream )
void Logger::log( const std::ostringstream& strStreamText, LoggerType eLogType ) {
    log( strStreamText.str().c_str(), eLogType );
} // log( const std::ostringstream )
// ----------------------------------------------------------------------------
// log( const char* )
void Logger::log( const char* szText, LoggerType eLogType ) {
    if ( nullptr == s_pLogger ) {
        std::cout << "Logger has not been initialized, can not log " << szText << std::endl;
        return;
    }
    BlockThread blockThread( s_criticalSection ); // Enter Critical Section
    std::ostringstream strStream;
    // Default White Text On Red Background
    WORD textColor = WHITE_ON_RED;
    // Chose Log Type Text String, Display "UNKNOWN" If eLogType Is Out Of Range
    strStream << std::setfill(' ') << std::setw( s_pLogger->m_uMaxCharacterLength );
    try {
        if ( TYPE_CONSOLE != eLogType ) {
            strStream << s_pLogger->m_aLogTypes.at( eLogType );
        }
        if ( TYPE_WARNING == eLogType ) {
            // Yellow
            textColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN;
        } else if ( TYPE_INFO == eLogType ) {
            // Green
            textColor = FOREGROUND_GREEN;
        } else if ( TYPE_CONSOLE == eLogType ) {
            // Cyan
            textColor = FOREGROUND_GREEN | FOREGROUND_BLUE;
        }
    } catch( ... ) {
        strStream << s_pLogger->m_strUnknownLogType;
    }
    // Date And Time
    if ( TYPE_CONSOLE != eLogType ) {
        SYSTEMTIME time;
        GetLocalTime( &time );
        strStream << " [" << time.wYear << "."
            << std::setfill('0') << std::setw( 2 ) << time.wMonth << "."
            << std::setfill('0') << std::setw( 2 ) << time.wDay << " "
            << std::setfill(' ') << std::setw( 2 ) << time.wHour << ":"
            << std::setfill('0') << std::setw( 2 ) << time.wMinute << ":"
            << std::setfill('0') << std::setw( 2 ) << time.wSecond << "."
            << std::setfill('0') << std::setw( 3 ) << time.wMilliseconds << "] ";
    }
    strStream << szText << std::endl;
    // Log Message
    SetConsoleTextAttribute( s_pLogger->m_hConsoleOutput, textColor );
    std::cout << strStream.str();
    // Save Message To Log File
    try {
        TextFileWriter file( s_pLogger->m_strLogFilename, true, false );
        file.write( strStream.str() );
    } catch( ... ) {
        // Not Saved In Log File, Write Message To Console
        std::cout << __FUNCTION__ << " failed to write to file: " << strStream.str() << std::endl;
    }
    // Reset To Default Color
    SetConsoleTextAttribute( s_pLogger->m_hConsoleOutput, s_pLogger->m_consoleDefaultColor );
} // log( const char* )

设置.h

#ifndef SETTINGS_H
#define SETTINGS_H
#include "Singleton.h"
class Settings sealed : public Singleton {
public:
    enum DebugLogging {
        DEBUG_NONE      = 0,
        DEBUG_MEMORY    = ( 1 << 0 ),
        DEBUG_RENDER    = ( 1 << 1 ),
        DEBUG_GUI       = ( 1 << 2 ),
        DEBUG_ALL       = 0xffffffff
    }; // DebugLogging
private:
    unsigned        m_uPhysicsRefreshRateHz;
    bool            m_isWindowedMode;
    unsigned long   m_uRandNumGenSeed;
    glm::uvec2      m_gamePixelSize;
    glm::uvec2      m_openglVersion;
    DebugLogging    m_eDebugLogging;
public:
    static Settings* const get();
    Settings();
    virtual ~Settings();
    std::string getNameAndVersion() const;
    void    setRandomNumberSeed( unsigned long uSeedValue );
    void    setWindowDisplayMode( bool isWindowed );
    bool    isWindowDisplayMode() const;
    void            setDebugLogging( DebugLogging eDebugLogging );
    void            setDebugLogging( unsigned uDebugLogging );
    DebugLogging    getDebugLogging() const;
    bool            isDebugLoggingEnabled( DebugLogging eDebugLogging ) const;
    void                setGameSize( const glm::uvec2& uGamePixelSize );
    const glm::uvec2&   getGameSize() const;
    double  getPhysicsStepSeconds() const;
    std::string showSummary() const;
    void                setOpenglVersion( const glm::uvec2& version );
    const glm::uvec2&   getOpenglVersion() const;
private:
    Settings( const Settings& c ); // Not Implemented
    Settings& operator=( const Settings& c ); // Not Implemented
}; // Settings
#endif // SETTINGS_H

设置.cpp

#include "stdafx.h"
#include "Settings.h"
#include "BuildConfig.h"
static Settings* s_pSettings = nullptr;
// ----------------------------------------------------------------------------
// get()
Settings* const Settings::get() {
    if ( nullptr == s_pSettings ) {
        throw ExceptionHandler( __FUNCTION__ + std::string( " failed, Settings has not been constructed yet" ) );
    }
    return s_pSettings;
} // get
// ----------------------------------------------------------------------------
// Settings()
Settings::Settings() :
Singleton( TYPE_SETTINGS ),
m_uPhysicsRefreshRateHz( 100 ), // Should Not Be Less Then 24Hz
m_isWindowedMode( false ),
m_uRandNumGenSeed( 0 ),
m_gamePixelSize( 1024, 768 ),
m_openglVersion( 0, 0 ),
m_eDebugLogging( DEBUG_NONE ) {
    s_pSettings = this;
    logMemoryAllocation( true );
} // Settings
// ----------------------------------------------------------------------------
// ~Settings()
Settings::~Settings() {
    logMemoryAllocation( false );
    s_pSettings = nullptr;
} // ~Settings
// ----------------------------------------------------------------------------
// getNameAndVersion()
std::string Settings::getNameAndVersion() const {
    std::ostringstream strStream;
    strStream << g_strGameName
        << " v" << g_iMajorVersion << "." << g_iMinorVersion << "." << g_iBuildNumber;
    return strStream.str();
} // getNameAndVersion
// ----------------------------------------------------------------------------
// setRandomNumberSeed()
void Settings::setRandomNumberSeed( unsigned long uSeedValue ) {
    m_uRandNumGenSeed = uSeedValue;
} // setRandomNumberSeed
// ----------------------------------------------------------------------------
// setWindowDisplayMode()
void Settings::setWindowDisplayMode( bool isWindowed ) {
    m_isWindowedMode = isWindowed;
} // setWindowDisplayMode
// ----------------------------------------------------------------------------
// isWindowDisplayMode()
bool Settings::isWindowDisplayMode() const {
    return m_isWindowedMode;
} // isWindowDisplayMode
// ----------------------------------------------------------------------------
// setDebugLogging()
void Settings::setDebugLogging( DebugLogging eDebugLogging ) {
    m_eDebugLogging = eDebugLogging;
} // setDebugLogging
// ----------------------------------------------------------------------------
// setDebugLogging()
void Settings::setDebugLogging( unsigned uDebugLogging ) {
    m_eDebugLogging = static_cast<Settings::DebugLogging>( uDebugLogging );
} // setDebugLogging
// ----------------------------------------------------------------------------
// getDebugLogging()
Settings::DebugLogging Settings::getDebugLogging() const {
    return m_eDebugLogging;
} // getDebugLogging
// ----------------------------------------------------------------------------
// isDebugLoggingEnabled()
bool Settings::isDebugLoggingEnabled( DebugLogging eDebugLogging ) const {
    return ( (m_eDebugLogging & eDebugLogging ) > 0 );
} // isDebugLoggingEnabled
// ----------------------------------------------------------------------------
// setGameSize()
void Settings::setGameSize( const glm::uvec2& uGamePixelSize ) {
    m_gamePixelSize = glm::uvec2( glm::clamp( uGamePixelSize.x, 800U, 2048U ),
                                  glm::clamp( uGamePixelSize.y, 600U, 2048U ) );
} // setGameSize
// ----------------------------------------------------------------------------
// getGameSize
const glm::uvec2& Settings::getGameSize() const {
    return m_gamePixelSize;
} // getGameSize
// ----------------------------------------------------------------------------
// getPhysicsStepSeconds()
double Settings::getPhysicsStepSeconds() const {
    return ( 1.0 / static_cast<double>( m_uPhysicsRefreshRateHz ) );
} // getPhysicsStepSeconds
// ----------------------------------------------------------------------------
// showSummary()
std::string Settings::showSummary() const {
    int iWidth = 53;
    std::ostringstream strStream;
    strStream << "Game Settings: " << std::endl;
    // OpenGL Version
    strStream << std::setfill(' ') << std::setw( iWidth ) << "OpenGL: " << m_openglVersion.x << "." << m_openglVersion.y << std::endl;
    // Random Number Generator Seed Value
    strStream << std::setfill(' ') << std::setw( iWidth ) << "Seed Value: " << m_uRandNumGenSeed << std::endl;
    // Render Mode And Size
    strStream << std::setfill(' ') << std::setw( iWidth ) << "Render Mode: " << ( m_isWindowedMode ? "Window" : "Full Screen" ) << std::endl;
    strStream << std::setfill(' ') << std::setw( iWidth ) << "Game Screen Resolution: " << m_gamePixelSize.x << "x" << m_gamePixelSize.y << " pixels" << std::endl;
    // Refresh Settings
    strStream << std::setfill(' ') << std::setw( iWidth ) << "Physics Refresh: " << m_uPhysicsRefreshRateHz << " Hz" << std::endl;
    return strStream.str();
} // showSummary
// ----------------------------------------------------------------------------
// setOpenglVersion()
void Settings::setOpenglVersion( const glm::uvec2& version ) {
    if ( version.x < 2 ) {
        // Using Older OpenGL 1.x
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " " << g_strGameName << " requires OpenGL v2+ to be supported";
        throw ExceptionHandler( strStream );
    }
    m_openglVersion = version;
} // setOpenglVersion
// ----------------------------------------------------------------------------
// getOpenglVersion()
const glm::uvec2& Settings::getOpenglVersion() const {
    return m_openglVersion;
} // getOpenglVersion

显然,这不会在您的机器上构建或编译,因为其中一些类依赖于此处未显示的其他类。然而,Singleton基类并不依赖于任何未显示的内容。有多种方法可以实现工作的单例对象。这些类对象的功劳归于Marek A.Krzeminski,可以在www.MarekKnows.com

上找到

您确实不需要单独的"Get"方法。对静态方法"Create"的调用应返回一个指向WindowManager实例的指针(如果不存在,则会创建)

class WindowManager
{
    private:
        static WindowManager*           s_wndmgr; //A singleton maintains a pointer to itself as a class variable
        //To ensure the integrity of a singleton
        //Constructors and the destructor should remain private
        WindowManager();
        ~WindowManager();
    public:
        static WindowManager* Create();
        static void Destroy();
        void Render();
}
WindowManager* WindowManager::s_wndmgr = NULL;
WindowManager::WindowManager()
{
}
WindowManager::~WindowManager()
{
}
WindowManager* WindowManager::Create()
{
    if ( !s_wndmgr ) 
    {
        s_wndmgr = new(std::nothrow) WindowManager();
    }
    return s_wndmgr;
}
void WindowManager::Destroy()
{
    if ( s_wndmgr )
    {
     delete s_wndmgr;
     s_wndmgr = NULL;
    }
}