一些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.
答案 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 ++