Qt使ctor调用两次

时间:2019-01-25 07:16:37

标签: c++ qt

一些Qt类要求必须首先构造QApplication。因此,我写了一些代码在适当的地方调用单例构造函数。这是一个“纯” C ++代码。

#include <functional>
#include <iostream>
#include <list>

#define INITIALIZABLE(NAME)                        \
private:                                            \
    template <typename T>                           \
    friend struct Initializer;                      \
    inline static NAME* m_instance = nullptr;       \
    static void create() { m_instance = new NAME; } \
    inline static Initializer<NAME> m_initializer{};


struct InitQueue {
    static std::list<std::function<void(void)>>& getList()
    {
        static std::list<std::function<void(void)>> s_list;
        return s_list;
    }
};

template <class T>
struct Initializer {
    Initializer()
    {
        auto initializer = []() {
            T::create();
        };

        InitQueue::getList().push_back(initializer);
    }
};

void initialize()
{
    for (auto func : InitQueue::getList()) {
        func();
    }
}

class Foo {
    INITIALIZABLE(Foo)

public:
    Foo()
    {
        m_count++;
        std::cout << "Ctor was called: " << m_count << "\n";
    }

private:
    static inline int m_count = 0;
};

int main(int, char**)
{
    initialize();
    return 0;
}

它按我的预期工作。但是然后我做了一个继承自QObject的Foo,所以Foo的ctor调用了两次。

Foo.cpp

#include "Foo.hpp"

Foo::Foo(QObject* parent)
    : QObject(parent)
{

    m_count++;
    std::cout << "Ctor was called: " << m_count << "\n";
}

std::list<std::function<void(void)>>& InitQueue::getList()
{
    static std::list<std::function<void(void)>> s_list;
    return s_list;
}

void initialize()
{
    for (auto func : InitQueue::getList()) {
        func();
    }
}

Foo.hpp

#pragma once

#include <functional>
#include <iostream>
#include <list>

#include <qobject.h>

#define INITIALIZABLE(NAME)                        \
private:                                            \
    template <typename T>                           \
    friend struct Initializer;                      \
    inline static NAME* m_instance = nullptr;       \
    static void create() { m_instance = new NAME; } \
    inline static Initializer<NAME> m_initializer{};

struct InitQueue {
    static std::list<std::function<void(void)>>& getList();
};

template <class T>
struct Initializer {
    Initializer()
    {
        auto initializer = []() {
            T::create();
        };

        InitQueue::getList().push_back(initializer);
    }
};

void initialize();

class Foo : public QObject {
    INITIALIZABLE(Foo)

public:
    Foo(QObject* parent = nullptr);

private:
    static inline int m_count = 0;
};

main.cpp

#include "Foo.hpp"

int main(int, char**)
{
    initialize();
    std::cin.get();
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.12)

project(DoubleCtorCall)

set(CMAKE_CXX_STANDARD 17)

#Qt specific
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

#Qt
find_package(Qt5Core CONFIG REQUIRED)

set(SOURCES 
    Main.cpp Foo.cpp Foo.hpp) 

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} Qt5::Core) 

所以。我有两个问题:为什么会发生这种情况以及如何避免(除了一次标记)?

Windows 10. MSVC 2017. Qt 5.12. 

2 个答案:

答案 0 :(得分:2)

在头文件中初始化静态成员变量时,将在包含头的每个转换单元中对其进行初始化。

在您的情况下,m_initializer被多次初始化,因为Qt moc系统生成了一些包含“ foo.h”的基础文件。您的InitQueue将包含多个初始化程序,这些初始化程序导致对Foo Ctor的多次调用。

仅分隔一个转换单元中定义的变量会有所帮助。例如:

#define ININIALIZEABLE(NAME)                        \
private:                                            \
    template <typename T>                           \
    friend struct Initializer;                      \
    static NAME* m_instance ;                       \
    static void create() { m_instance = new NAME; } \
    static Initializer<NAME> m_initializer;

#define IMPL_INITIALIZEABLE(NAME) \
    NAME* NAME::m_instance = nullptr;\
    Initializer<NAME> NAME::m_initializer{};

然后在您的foo.cpp中使用宏:

IMPL_INITIALIZEABLE(Foo)

Foo::Foo(QObject* parent)
    : QObject(parent)
{
    m_count++;
    std::cout << "Ctor was called: " << m_count << "\n";
}

答案 1 :(得分:1)

我无法直接回答问题。我只能这么大胆地建议,那不是“纯C ++”代码。每当需要标准C ++“一次且仅一次”时,就会使用以下代码:

//  header only
//  never anonymous namespace
namespace my_space 
{
inline single_thing_type const & make_once 
  ( /* if required arguments for initializaton go here */ )
 {
      auto initor_ = [&](){
          /* this is where it is made */
          static single_thing_type single_instance ;
          /*
              or calling some non-default ctor, or some factory
              and doing whatever is required by single_thing_type
              to be made and initialised
           */
          return single_instance ;
      };
      /* this is resilient in presence of multiple threads */
      static single_thing_type const & singleton_ = initor_()  ;
      /* 
           return by ref. so type can be 
           non-movable and non-copyable
           if required
       */
      return singleton_ ;
 }

   // here we can provide a process-wide global if required
   inline single_thing_type const & 
        single_global_made_once = make_once () ;
 } // my_space

有很多变体,但这是成语的核心。我确信可以使用Qt将其应用于标准C ++的上下文中。

不是Qt代码,而是从上面使用Foo类的简化的仍然正确的版本:

namespace my_space 
{
  struct Foo {
   Foo()
    {
    std::cout << "Foo() ctor called.\n";
    }
  }; // Foo

inline Foo const & make_once (  )
 {
      auto initor_ = [&](){
          static Foo single_instance ;
          return single_instance ;
      };
      static Foo const & singleton_ = initor_()  ;
      return singleton_ ;
 }

   inline Foo const & 
        single_global_foo_made_once = make_once () ;
 } // my_space

 int main () 
 {
    using namespace my_space;
        auto const &  it_is_already_made 
             = single_global_foo_made_once ;
 }

我并不是说我已经发明了这个。为了overview and details see here。 这个习惯用法不需要​​以任何方式更改所处理的类型。也许您可以尝试使用所需的Qt类型。

所谓的“ Scot Meyers Singleton”中没有使用lambda(上面的“ initor”)。一个好用例是创建一些event_log的单个实例。

  inline event_log const & const & make_event_log_once (  )
 {
      auto initor_ = [&](){
          auto event_log_file_name 
              =  read_it_from_environemnt_confif_or_whatever() ;
          auto event_log_file_path 
             =  ensure_platform_and_folder (event_log_file_name )  ;
          return event_log( event_log_file_path ) ;
      };
      static event_log singleton_{ initor_() }  ;
      return singleton_ ;
 }

   inline event_log const & 
        event_log_instance = make_event_log_once () ;

在创建事件日志类实例之前,我们需要以某种方式从某处获取文件名。然后,我们需要从中建立完整路径,确保平台正确,并确保该平台上的文件夹。此时,我们可以创建event_log的实例。这就是我们在initor lambda中所做的,知道所有这些仅被调用一次。

享受标准的C ++