簡體   English   中英

如何在 C++ 中延遲靜態對象(記錄器)的銷毀?

[英]How to delay destruction of static object (logger) in C++?

我有一個類,它將應用程序的記錄器保存為 unique_ptr。 可以通過靜態函數設置記錄器。 此外,顯然可以記錄消息。 我省略了任何線程同步(互斥鎖)以使事情變得更容易。

class LoggerWrapper {
 public:
  static void SetLogger(std::unique_ptr<logger::ILogger> new_logger);

  static void Log(const std::string& message);

 private:
  static std::unique_ptr<logger::ILogger> logger_;
};
void LoggerWrapper::Log(const std::string& message) {
  if (!logger_) {
    // cannot log
  } else {
    logger_->OnLogEvent(message);
  }
}

void LoggerWrapper::SetLogger(std::unique_ptr<logger::ILogger> new_logger) {
  logger_ = std::move(new_logger);
}

我的問題是: unique_ptr 在應用程序中的其他一些類之前被破壞。 例如, Class Foo的 DTOR 想要記錄一些東西,unique_ptr 可能已經被銷毀(目前就是這種情況)。 這會導致ILogger實現被破壞,導致無法輸出日志。

有沒有人知道如何輕松解決這個問題? 我不知何故需要“延遲”靜態unique_ptr的破壞。 我還嘗試將其更改為 shared_ptr,但這只會導致 SIGABRT 出現“調用純虛擬方法”錯誤。

提前致謝!

編輯:創建了一個與我的經驗相矛盾的最小工作示例。 在這種情況下,靜態記錄器的壽命比Foo類長。

EDIT2:我的應用程序使用exit 這似乎改變了破壞的順序。

EDIT3: exit破壞本地對象。

/******************************************************************************

                              Online C++ Compiler.
               Code, Compile, Run and Debug C++ program online.
Write your code in this editor and press "Run" button to compile and execute it.

*******************************************************************************/

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class ILogger {
  public:
  ILogger() {
      std::cout << "ILogger CTOR" << std::endl;
  }
  ~ILogger() {
      std::cout << "ILogger DTOR" << std::endl;
  }
  
  virtual void OnLogEvent(const std::string& log_message) {
        std::cout << "OnLogEvent: " << log_message << std::endl;
  }
};

class LoggerWrapper {
 public:
  static void SetLogger(std::unique_ptr<ILogger> new_logger) {
  logger_ = std::move(new_logger);
}

  static void Log(const std::string& message) {
  if (!logger_) {
    // cannot log
  } else {
    logger_->OnLogEvent(message);
  }
};

 private:
  static std::unique_ptr<ILogger> logger_;
};

class Foo {
  public:
   Foo(const std::string& name) : name_{name} {
       LoggerWrapper::Log(name_ + ": CTOR");
   }
   ~Foo() {
       LoggerWrapper::Log(name_ + ": DTOR");
   }
  private:
   std::string name_;
};

// declaring logger_ first causes it to be deleted AFTER foo
std::unique_ptr<ILogger> LoggerWrapper::logger_;
std::unique_ptr<Foo> foo;


int main()
{
    LoggerWrapper::SetLogger(std::make_unique<ILogger>());
    foo = std::make_unique<Foo>("Global FOO");
    
    // local variables do NOT get destroyed when calling exit!
    auto foo_local = Foo("Local FOO");

    exit(1);
}

這是微不足道的。

首先,您不使用全局靜態對象(您不應該使用這樣的全局狀態)。 您使用函數靜態對象,以便您可以控制創建/銷毀的順序。

所以改變這個:

std::unique_ptr<ILogger> LoggerWrapper::logger_;
std::unique_ptr<Foo> foo;

進入:

 class GlobalLogger
 {
     public:
         ILogger& getLogger() {
             static ILogger  logger;  // if you must use unique_ptr you can do that here
             return logger;           // But much simpler to use a normal object.
         }
  };
  class GlobalFoo
  {
      public:
          Foo& getFoo() {
              // If there is a chance that foo is going to 
              // use global logger in its destructor
              // then it should simply call `GlobalLogger::getLogger()`
              // in the constructor of Foo. You then 
              // guarantee the order of creation and thus destruction.
              // Alternatively, you can call it here in thus
              // function just before the declaration of foo.
              static Foo foo;
              return foo;
          }
  };

  // Where you were using `logger_` use `GlobalLogger::getLogger()`
  // Where you were using `foo`     use `GlobalFoo::getFoo()`

如果我們使用您的原始代碼作為起點,我們可以這樣做:

#include <iostream>
#include <memory>
#include <string>

// Please don't do this.
// This is the number one worst practice.
// https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice
using namespace std;

class ILogger {
    public:
        ILogger() {
            std::cout << "ILogger CTOR" << std::endl;
        }
        ~ILogger() {
            std::cout << "ILogger DTOR" << std::endl;
        }

        virtual void OnLogEvent(const std::string& log_message) {
            std::cout << "OnLogEvent: " << log_message << std::endl;
        }
};

class LoggerWrapper
{
    // Here store the logger
    // as a static member of this private function.
    // The the SetLogger() Log() functions get this reference.
    static std::unique_ptr<ILogger>& getLogReference() {
        static std::unique_ptr<ILogger> logger;
        return logger;
    }

    public:
        static void SetLogger(std::unique_ptr<ILogger> new_logger) {
            // Save the new reference.
            getLogReference() = std::move(new_logger);
        }

        // Use the logger if it has been set.
        static void Log(const std::string& message) {
            std::unique_ptr<ILogger>& logger_ = getLogReference();
            if (!logger_) {
                // cannot log
            } else {
                logger_->OnLogEvent(message);
            }
        };
};

class Foo {
    public:
        Foo(const std::string& name) : name_{name} {
            // This calls Log()
            // Which calls getLogReference()
            // Which forces the creation of the function static
            // variable logger so it is created before this
            // object is fully initialized (if it has not already
            // been created).
            // 
            // This means this object was created after the logger
            LoggerWrapper::Log(name_ + ": CTOR");
        }
        ~Foo() {
            // Because the Log() function was called in the
            // constructor we know the loger was fully constructed first
            // thus this object will be destroyed first
            // so the logger object is guaranteed to be
            // available in this objects destructor
            // so it is safe to use.
            LoggerWrapper::Log(name_ + ": DTOR");
        }
    private:
        std::string name_;
};


std::unique_ptr<Foo>& globalFoo() {
    // foo may destroy an object created later
    // that has a destructor that calls LoggerWrapper::Log()
    // So we need to call the Log function here before foo
    // is created.
    LoggerWrapper::Log("Initializing Global foo");
    // Note: Unless somebody else has explicitly called SetLogger()
    // the above line is unlikely to log anything as the logger
    // will be null at this point.

    static std::unique_ptr<Foo> foo;
    return foo;
}

int main()
{
    LoggerWrapper::SetLogger(std::make_unique<ILogger>());
    globalFoo() = std::make_unique<Foo>("Global FOO");

    // local variables do NOT get destroyed when calling exit!
    auto foo_local = Foo("Local FOO");

    exit(1);
}

創建和銷毀靜態(全局)對象的順序是未定義的。 這給你留下了幾個選擇。

  1. 不要使用全局對象,只使用原始使用指針,您可以按照正確的順序銷毀自己。
  2. 使用一個指向你的記錄器的指針,你永遠不會破壞它。 這在技術上是內存泄漏,但內核會在您的應用程序退出時進行清理。
  3. 為您的記錄器使用shared_ptr 每個使用記錄器的對象都會獲得一個shared_ptr並且在最后一個對象被銷毀后記錄器將被清理。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM