1

Some of Qt classes require that QApplication must be constructed first. So, I wrote some code for calling singleton constructor in an appropriate place. Here is a "pure" C++ code for this.

#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;
}

It worked as I expected. BUT then I make a Foo Inherited from QObject, somehow ctor of Foo is calling twice.

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) 

So. I have two questions: Why does this happened and how to avoid it (except once flags)?

Windows 10. MSVC 2017. Qt 5.12. 

2 Answers2

1

I can not answer the question directly. I can only be so bold to advise, that is not a "pure C++" code. I am using the following whenever I need "make it once and only once" standard C++, coding idiom:

//  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

There are many variations but this is the core of the idiom. I am sure it can be applied in the context of standard C++, using the Qt.

Not a Qt code bellow, is using the simplified still correct version of your class Foo from above:

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 ;
 }

I am not claiming I have invented this. For a good overview and details see here. This idiom does not require the type handled, to be changed in any way. Perhaps you can try it on Qt types you need.

What is not in the so-called "Scot Meyers Singleton" is a use of the lambda ('initor' above). An good use case is creating a single instance of some 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 () ;

Before we create the event log class instance we need to obtain the file name somehow and from somewhere. Then we need to make a full path form it, making sure platform is right and folder on that platform is assured. Just then we can make the instance of the event_log. This is what we do in the initor lambda, knowing all of that will be called only once.

Enjoy the standard C++

Chef Gladiator
  • 902
  • 11
  • 23
  • Thanks for your advice, I'll try it tomorrow and let you know if it works. "Pure C++" in my case - without Qt specific stuff, like moc and other "hidden" code generation. –  Jan 25 '19 at 09:26
  • So, I looked in to your code and as far as I can say it’s just another version of Scott Meyers Singleton. Unfortunately, this Implementation lead to some difficult with Qt property system and QML integration. –  Jan 28 '19 at 04:01
  • @YouDoItWrong this idiom does not impose any requirements on the type whose instance has to be made only once. SM "Singleton" (as far as I can remember) is a c++ class design pattern. If class Foo does not inherit but contains a member to the Qt type required, will that work? Can you please establish a project on wandbox and send us a link? – Chef Gladiator Jan 28 '19 at 10:33
  • @YouDoItWrong I was not claiming I invented this idiom. https://stackoverflow.com/questions/17712001/how-is-meyers-implementation-of-a-singleton-actually-a-singleton – Chef Gladiator Jan 28 '19 at 11:00
  • SM singleton is just one of the C++ specific variations of singleton which involve a function local static storage. What am I trying to say? Your singleton implementation based on a function local static, same as SM singleton. Using this type of singleton in my Qt cases lead to: object slicing and errors at app shutdown. >> If class Foo does not inherit but contains a member to the Qt type required, will that work? No, it will not work. (You must reimplement internal Qt subsystems and make them compatible). –  Jan 30 '19 at 04:09
  • @YouDoItWrong ok, to fully understand your exact problem we need a wandbox share. your question contains a lot of code which is irrelevant it seems. Can we please have the canonical example on the Wandbox? Qt is irrelevatn here. – Chef Gladiator Jan 30 '19 at 08:59
  • @YouDoItWrong ignore the previous comment I could not edit it due to SO "rules". What am I tryinmg to say: can you please edit your question so we have the simple cannonical example? Withot the `IntiQueue` at least. And without this macro. I am trying to help, and you give me back unnecessary debate on simple standard C++ mature idiom which is proven countless of times. – Chef Gladiator Jan 30 '19 at 09:13
1

When you initialize static member variables in the header file, it is going to be initialized in each translation unit where the header is included.

In your case, m_initializer was initialized multiple times because Qt moc system generated some underlying files that include your "foo.h". Your InitQueue will contain multiple initializers that result in multiple calls to Foo Ctor.

Separating the variables defined in only one translation unit will help. For example :

#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{};

Then use the macro in your foo.cpp :

IMPL_INITIALIZEABLE(Foo)

Foo::Foo(QObject* parent)
    : QObject(parent)
{
    m_count++;
    std::cout << "Ctor was called: " << m_count << "\n";
}
tunglt
  • 1,022
  • 1
  • 9
  • 16