Qt сделать вызов ctor дважды - PullRequest
0 голосов
/ 25 января 2019

Некоторые классы 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;
}

Это сработало, как я и ожидал. НО тогда я делаю Foo Inherited от QObject, как-то ctor из Foo вызывает дважды.

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 голосов
/ 25 января 2019

Когда вы инициализируете статические переменные-члены в файле заголовка, он будет инициализироваться в каждой единице перевода, в которую включен заголовок.

В вашем случае 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";
}
0 голосов
/ 25 января 2019

Я не могу ответить на вопрос напрямую.Я могу только быть настолько смелым, чтобы советовать, что это не «чистый 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

Есть много вариантов, но это ядро ​​идиомы.Я уверен, что это может быть применено в контексте стандарта C ++, используя Qt.

Не приведенный ниже код 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 ;
 }

Я не утверждаю, что изобрел это.Хороший обзор и подробности смотрите здесь .Эта идиома не требует, чтобы обрабатываемый тип был изменен каким-либо образом.Возможно, вы можете попробовать это на нужных вам типах Qt.

То, чего нет в так называемом «Scot Meyers Singleton», - это использование лямбда-выражения («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 лямбде, зная, что все это будет вызываться только один раз.

Наслаждайтесь стандартом C ++

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...