C++实现一个简单的Qt信号槽机制

news2025/1/23 7:17:35

昨天写这个文章《深入探讨C++的高级反射机制(2):写个能用的反射库》的时候就在想,是不是也能在这套反射逻辑的基础上,实现一个类似Qt的信号槽机制?

Qt信号槽机制简介

所谓的Qt的信号槽(Signals and Slots)机制,是Qt框架中实现对象之间通信的一种方式。这是一个事件驱动程序中常见的设计模式。信号槽机制允许创建响应特定事件(如用户交互、数据变化等)的可重用组件。
信号槽主要有以下核心概念组成:

信号(Signals)

信号是一个类成员函数的声明,它在类内部以 signals: 关键词标识。当某个事件发生时,可以发射(emit)信号。信号不包含具体的实现代码,只是一个通知机制。它告诉外界某个事件已经发生,比如按钮被点击或者定时器超时。

槽(Slots)

槽是一个普通的成员函数,可以是公有的、保护的或私有的,它在类内部以 slots: 关键词标识(Qt 5 开始,普通的成员函数也可以作为槽)。槽函数包含了当信号发射时应该执行的代码。换句话说,槽函数是对信号的响应。

连接(Connection)

信号和槽之间的连接是通过 QObject::connect() 函数建立的。这个连接指定了当信号发射时,应该调用哪个槽函数。一个信号可以连接到多个槽,一个槽也可以接收来自多个信号的通知。

示例

以下是一个简单的Qt信号和槽的例子,展示了这个机制如何工作:

#include <QObject>

class Button : public QObject {
    Q_OBJECT

public:
    Button() {}

signals:
    void clicked(); // 信号声明

public slots:
    void onClick() { // 槽声明
        // 处理按钮点击事件
    }
};

int main() {
    Button button;
    // 连接按钮的 clicked 信号到同一个按钮的 onClick 槽
    QObject::connect(&button, &Button::clicked, &button, &Button::onClick);

    // 在某个地方,按钮被点击,发射信号
    emit button.clicked();

    return 0;
}

#include "main.moc" // 如果使用qmake或CMake,通常不需要这一行

在这个例子中,当按钮被点击时,它会发射 clicked 信号,这会导致调用与它连接的 onClick 槽函数。

信号槽机制的优点在于它提供了一种松耦合的方式来处理事件。对象不需要知道哪些对象或函数对它们的信号感兴趣,它们只需在合适的时候发射信号。这样可以创建可重用和可维护的组件,同时简化了应用程序的事件处理逻辑。

我们的实现思路

为了实现类似于Qt信号槽的机制,我们需要一个类似QObject的基类。为了避免引入新概念,我们这个类也直接较QObject好了。类中实现信号的发射(emit)和槽的连接(connect)。
笔者不太喜欢Qt的connect函数是个静态函数,所以我们这里的实现稍微和Qt不一样,我们的connect函数是个普通成员函数,用于将自己的信号连接到目标槽上。
接下来,我们需要声明信号的机制。我们通过定义宏DECL_SIGNAL来声明一个信号,并实现相应的连接和断开连接的逻辑。
于是,我们的信号槽大概用法如下:


// 用户自定义的结构体
class MyStruct : public refl::QObject // 信号槽等功能从这个类派生
{
public:
	// 定义一个方法,用作槽函数,必须在REFLECTABLE_MENBER_FUNCS列表中,并且参数必须是std::any,不能超过4个参数。
	std::any on_x_value_modified(std::any new_value) {
		int value = std::any_cast<int>(new_value);
		std::cout << "MyStruct::on_x_value_modified called! New value is: " << value << std::endl;
		return 0;
	}
	REFLECTABLE_MENBER_FUNCS(MyStruct,
		REFLEC_FUNCTION(on_x_value_modified)
	);
	DECL_SIGNAL(x_value_modified, int) // 声明信号x_value_modified
	DECL_DYNAMIC_REFLECTABLE(MyStruct)//动态反射的支持
};

// 信号槽的连接和调用:

MyStruct obj1;
MyStruct obj2;

// 连接obj1的信号到obj2的槽函数
size_t connection_id = obj1.**connect**("x_value_modified", &obj2, "on_x_value_modified");
if (connection_id != 0) {
	std::cout << "Signal x_value_modified from obj1 connected to on_x_value_modified slot in obj2." << std::endl;
}
obj1.x_value_modified(42);// 触发信号

// 断开连接
obj1.**disconnect**(connection_id);
// 再次触发信号,应该没有任何输出,因为已经断开连接
obj1.x_value_modified(84);

有了用法的情况下,我们就有了目标了。
这个是我们DECL_SIGNAL和QObject的实现:


	//宏用于类中声明信号,并提供一个同名的方法来触发信号。
	#define DECL_SIGNAL(signal_name, ...) \
		template<typename... Args> \
		void signal_name(Args&&... args) { \
			emit_signal_impl(#signal_name, std::forward<Args>(args)...); \
		} \

	class QObject : public refl::dynamic::IReflectable {
	private:
		// 信号与槽的映射,键是信号名称,值是一组槽函数的信息
		std::unordered_map<std::string, std::vector<std::pair<QObject*, std::string>>> connections;
		size_t next_connection_id = 1;
		std::map<size_t, std::pair<std::string, std::pair<QObject*, std::string>>> connection_map;

	public:
		template<typename... Args>
		void emit_signal_impl(const char* signal_name, Args&&... args) {
			auto it = connections.find(signal_name);
			if (it != connections.end()) {
				for (auto& slot_info : it->second) {
					slot_info.first->invoke_member_func_by_name(slot_info.second.c_str(), std::forward<Args>(args)...);
					//invoke_member_func_type_safe(*slot_info.first, slot_info.second.c_str(), std::forward<Args>(args)...); 
				}
			}
		}

		size_t connect(const char* signal_name, QObject* target, const char* target_member_func_name) {
			if (!target || !signal_name || !target_member_func_name) return 0;
			connections[signal_name].emplace_back(target, target_member_func_name);
			size_t id = next_connection_id++;
			connection_map[id] = { signal_name, {target, target_member_func_name} };
			return id;
		}

		bool disconnect(size_t connection_id) {
			auto it = connection_map.find(connection_id);
			if (it != connection_map.end()) {
				auto& [signal_name, slot_info] = it->second;
				auto& slots = connections[signal_name];
				slots.erase(std::remove(slots.begin(), slots.end(), slot_info), slots.end());
				connection_map.erase(it);
				return true;
			}
			return false;
		}
	};

运行起来,还不错:
在这里插入图片描述

但是这段代码很不优雅:

size_t connection_id = obj1.connect("x_value_modified", &obj2, "on_x_value_modified");

因为都是字符串,万一打错了单词还不容易发现。我们是否可以优化成这种形式:

size_t connection_id = obj1.connect(&MyStruct::x_value_modified, &obj2, &MyStruct::on_x_value_modified);

实现这种形式也不难,我们需要对connect方法进行重载,使其能接受成员函数指针而不是字符串。并能从成员函数指针中提取其函数名称。

template <typename SignalClass, typename SignalType, typename SlotClass, typename SlotType>
	size_t connect(SignalType SignalClass::*signal, SlotClass* slot_instance, SlotType SlotClass::*slot) {
		const char* signal_name = get_member_func_name<SignalClass>(signal);
		const char* slot_name = get_member_func_name<SlotClass>(slot);
		if (signal_name && slot_name) {
			return connect(signal_name, static_cast<QObject*>(slot_instance), slot_name);
		}
		return 0; // Failed
	}

由于我们已经有了之前反射库的实现经验,get_member_func_name的实现也信手拈来:

template <typename T, typename FuncTuple, size_t N = 0>
constexpr const char* __get_member_func_name_impl(void* func_ptr, const FuncTuple& tp) {
	if constexpr (N >= std::tuple_size_v<FuncTuple>) {
		return nullptr; // Not Found!
	} else {
		const auto& func = std::get<N>(tp);
		if (reinterpret_cast<void*>(func.get_func()) == func_ptr) {
			return func.name;
		} else {
			return __get_member_func_name_impl<T, FuncTuple, N + 1>(func_ptr, tp);
		}
	}
}

template <typename T, typename FuncPtr>
constexpr const char* get_member_func_name(FuncPtr func_ptr) {
	constexpr auto funcs = T::member_funcs();
	return __get_member_func_name_impl<T>(reinterpret_cast<void*>(func_ptr), funcs);
}

不过编译下来,发现这种做法有点问题,前面DECL_SIGNAL声明的是一个变参模板函数,导致无法对其进行取地址:

#define DECL_SIGNAL(signal_name, ...) \
		template<typename... Args> \
		void signal_name(Args&&... args) { \
			emit_signal_impl(#signal_name, std::forward<Args>(args)...); \
		} \

直接取地址是会报错的:
在这里插入图片描述
经过一轮思索,于是把DECL_SIGNAL的使用形式改为:IMPL_SIGNAL:

	//宏用于类中声明信号,并提供一个同名的方法来触发信号。示例:
	/*	void x_value_modified(int param) {
		IMPL_SIGNAL(param);
	}*/
	#define IMPL_SIGNAL(...) raw_emit_signal_impl(__func__ , __VA_ARGS__)

于是,前面的类声明信号的部分由

DECL_SIGNAL(x_value_modified, int) // 声明信号x_value_modified

变为:

void x_value_modified(int param) {IMPL_SIGNAL(param);}

新的定义提供了更好的类型安全保障,避免参数个数和类型传错了导致发射信号失败。同时兼容我们更安全的connect的版本。
在这里插入图片描述

好了,就先这样吧。以后有时间继续优化。

这次完整的代码如下:

#include <iostream>
#include <tuple>
#include <stdexcept>
#include <assert.h>
#include <string_view>
#include <optional>
#include <utility> // For std::forward
#include <unordered_map>
#include <functional>
#include <memory>
#include <any>
#include <type_traits> // For std::is_invocable
#include <map>

namespace refl {

	// 这个宏用于创建字段信息
#define REFLECTABLE_PROPERTIES(TypeName, ...)  using CURRENT_TYPE_NAME = TypeName; \
    static constexpr auto properties() { return std::make_tuple(__VA_ARGS__); }
#define REFLECTABLE_MENBER_FUNCS(TypeName, ...) using CURRENT_TYPE_NAME = TypeName; \
    static constexpr auto member_funcs() { return std::make_tuple(__VA_ARGS__); }

// 这个宏用于创建属性信息,并自动将字段名转换为字符串
#define REFLEC_PROPERTY(Name) refl::Property<decltype(&CURRENT_TYPE_NAME::Name), &CURRENT_TYPE_NAME::Name>(#Name)
#define REFLEC_FUNCTION(Func) refl::Function<decltype(&CURRENT_TYPE_NAME::Func), &CURRENT_TYPE_NAME::Func>(#Func)

// 定义一个属性结构体,存储字段名称和值的指针
	template <typename T, T Value>
	struct Property {
		const char* name;
		constexpr Property(const char* name) : name(name) {}
		constexpr T get_value() const { return Value; }
	};
	template <typename T, T Value>
	struct Function {
		const char* name;
		constexpr Function(const char* name) : name(name) {}
		constexpr T get_func() const { return Value; }
	};

	// 使用 std::any 来处理不同类型的字段值和函数返回值
	template <typename T, typename Tuple, size_t N = 0>
	std::any __get_field_value_impl(T& obj, const char* name, const Tuple& tp) {
		if constexpr (N >= std::tuple_size_v<Tuple>) {
			return std::any();// Not Found!
		}
		else {
			const auto& prop = std::get<N>(tp);
			if (std::string_view(prop.name) == name) {
				return std::any(obj.*(prop.get_value()));
			}
			else {
				return __get_field_value_impl<T, Tuple, N + 1>(obj, name, tp);
			}
		}
	}

	// 使用 std::any 来处理不同类型的字段值和函数返回值
	template <typename T, size_t N = 0>
	std::any get_field_value(T& obj, const char* name) {
		return __get_field_value_impl(obj, name, T::properties());
	}

	// 使用 std::any 来处理不同类型的字段值和函数返回值
	template <typename T, typename Tuple, typename Value, size_t N = 0>
	std::any __assign_field_value_impl(T& obj, const char* name, const Value& value, const Tuple& tp) {
		if constexpr (N >= std::tuple_size_v<Tuple>) {
			return std::any();// Not Found!
		}
		else {
			const auto& prop = std::get<N>(tp);
			if (std::string_view(prop.name) == name) {
				if constexpr (std::is_assignable_v<decltype(obj.*(prop.get_value())), Value>) {
					obj.*(prop.get_value()) = value;
					return std::any(obj.*(prop.get_value()));
				}
				else {
					assert(false);// 无法赋值 类型不匹配!!
					return std::any();
				}
			}
			else {
				return __assign_field_value_impl<T, Tuple, Value, N + 1>(obj, name, value, tp);
			}
		}
	}
	template <typename T, typename Value>
	std::any assign_field_value(T& obj, const char* name, const Value& value) {
		return __assign_field_value_impl(obj, name, value, T::properties());
	}

	// 成员函数调用相关:
	template <bool assert_when_error = true, typename T, typename FuncTuple, size_t N = 0, typename... Args>
	constexpr std::any __invoke_member_func_impl(T& obj, const char* name, const FuncTuple& tp, Args&&... args) {
		if constexpr (N >= std::tuple_size_v<FuncTuple>) {
			assert(!assert_when_error);// 没找到!
			return std::any();// Not Found!
		}
		else {
			const auto& func = std::get<N>(tp);
			if (std::string_view(func.name) == name) {
				if constexpr (std::is_invocable_v<decltype(func.get_func()), T&, Args...>) {
					if constexpr (std::is_void<decltype(std::invoke(func.get_func(), obj, std::forward<Args>(args)...))>::value) {
						// 如果函数返回空,那么兼容这种case
						std::invoke(func.get_func(), obj, std::forward<Args>(args)...);
						return std::any();
					}
					else {
						return std::invoke(func.get_func(), obj, std::forward<Args>(args)...);
					}
				}
				else {
					assert(!assert_when_error);// 调用参数不匹配
					return std::any();
				}
			}
			else {
				return __invoke_member_func_impl<assert_when_error, T, FuncTuple, N + 1>(obj, name, tp, std::forward<Args>(args)...);
			}
		}
	}

	template <typename T, typename... Args>
	constexpr std::any invoke_member_func(T& obj, const char* name, Args&&... args) {
		constexpr auto funcs = T::member_funcs();
		return __invoke_member_func_impl(obj, name, funcs, std::forward<Args>(args)...);
	}

	template <typename T, typename... Args>
	constexpr std::any invoke_member_func_safe(T& obj, const char* name, Args&&... args) {
		constexpr auto funcs = T::member_funcs();
		return __invoke_member_func_impl<true>(obj, name, funcs, std::forward<Args>(args)...);
	}

	template <typename T, typename FuncPtr, typename FuncTuple, size_t N = 0>
	constexpr const char* __get_member_func_name_impl(FuncPtr func_ptr, const FuncTuple& tp) {
		if constexpr (N >= std::tuple_size_v<FuncTuple>) {
			return nullptr; // Not Found!
		}
		else {
			const auto& func = std::get<N>(tp);
			if constexpr (std::is_same< decltype(func.get_func()), FuncPtr >::value) {
				return func.name;
			}
			else {
				return __get_member_func_name_impl<T, FuncPtr, FuncTuple, N + 1>(func_ptr, tp);
			}
		}
	}

	template <typename T, typename FuncPtr>
	constexpr const char* get_member_func_name(FuncPtr func_ptr) {
		constexpr auto funcs = T::member_funcs();
		return __get_member_func_name_impl<T, FuncPtr>(func_ptr, funcs);
	}


	// 定义一个类型特征模板,用于获取属性信息
	template <typename T>
	struct For {
		static_assert(std::is_class_v<T>, "Reflector requires a class type.");

		// 遍历所有字段名称
		template <typename Func>
		static void for_each_propertie_name(Func&& func) {
			constexpr auto props = T::properties();
			std::apply([&](auto... x) {
				((func(x.name)), ...);
				}, props);
		}

		// 遍历所有字段值
		template <typename Func>
		static void for_each_propertie_value(T& obj, Func&& func) {
			constexpr auto props = T::properties();
			std::apply([&](auto... x) {
				((func(x.name, obj.*(x.get_value()))), ...);
				}, props);
		}

		// 遍历所有函数名称
		template <typename Func>
		static void for_each_member_func_name(Func&& func) {
			constexpr auto props = T::member_funcs();
			std::apply([&](auto... x) {
				((func(x.name)), ...);
				}, props);
		}
	};

	// ===============================================================

	// 以下是动态反射机制的支持代码:
	namespace dynamic {
		// 反射基类
		class IReflectable {
		public:
			virtual ~IReflectable() = default;
			virtual std::string_view get_type_name() const = 0;

			virtual std::any get_field_value_by_name(const char* name) const = 0;

			virtual std::any invoke_member_func_by_name(const char* name) = 0;
			virtual std::any invoke_member_func_by_name(const char* name, std::any param1) = 0;
			virtual std::any invoke_member_func_by_name(const char* name, std::any param1, std::any param2) = 0;
			virtual std::any invoke_member_func_by_name(const char* name, std::any param1, std::any param2, std::any param3) = 0;
			virtual std::any invoke_member_func_by_name(const char* name, std::any param1, std::any param2, std::any param3, std::any param4) = 0;
			// 不能无限增加,会增加虚表大小。最多支持4个参数的调用。
		};

		// 类型注册工具
		class TypeRegistry {
		public:
			using CreatorFunc = std::function<std::unique_ptr<IReflectable>()>;

			static TypeRegistry& instance() {
				static TypeRegistry registry;
				return registry;
			}

			void register_type(const std::string_view type_name, CreatorFunc creator) {
				creators[type_name] = std::move(creator);
			}

			std::unique_ptr<IReflectable> create(const std::string_view type_name) {
				if (auto it = creators.find(type_name); it != creators.end()) {
					return it->second();
				}
				return nullptr;
			}

		private:
			std::unordered_map<std::string_view, CreatorFunc> creators;
		};

		// 用于注册类型信息的宏
#define DECL_DYNAMIC_REFLECTABLE(TypeName) \
    friend class refl::dynamic::TypeRegistryEntry<TypeName>; \
    static std::string_view static_type_name() { return #TypeName; } \
    virtual std::string_view get_type_name() const override { return static_type_name(); } \
    static std::unique_ptr<::refl::dynamic::IReflectable> create_instance() { return std::make_unique<TypeName>(); } \
    static const bool is_registered; \
    std::any get_field_value_by_name(const char* name) const override { \
        return refl::get_field_value(*this, name); \
    } \
    std::any invoke_member_func_by_name(const char* name) override { \
        return refl::invoke_member_func(*static_cast<TypeName*>(this), name); \
    }\
	std::any invoke_member_func_by_name(const char* name, std::any param1) override { \
		return refl::invoke_member_func(*static_cast<TypeName*>(this), name, param1); \
	}\
	std::any invoke_member_func_by_name(const char* name, std::any param1, std::any param2) override { \
		return refl::invoke_member_func(*static_cast<TypeName*>(this), name, param1, param2); \
	}\
	std::any invoke_member_func_by_name(const char* name, std::any param1, std::any param2, std::any param3) override { \
		return refl::invoke_member_func(*static_cast<TypeName*>(this), name, param1, param2, param3); \
	}\
	std::any invoke_member_func_by_name(const char* name, std::any param1, std::any param2, std::any param3, std::any param4) override { \
		return refl::invoke_member_func(*static_cast<TypeName*>(this), name, param1, param2, param3, param4); \
	}\

	// 用于在静态区域注册类型的辅助类
		template <typename T>
		class TypeRegistryEntry {
		public:
			TypeRegistryEntry() {
				::refl::dynamic::TypeRegistry::instance().register_type(T::static_type_name(), &T::create_instance);
			}
		};

		// 为每个类型定义注册变量,这段宏需要出现在cpp中。
#define REGEDIT_DYNAMIC_REFLECTABLE(TypeName) \
    const bool TypeName::is_registered = [] { \
        static ::refl::dynamic::TypeRegistryEntry<TypeName> entry; \
        return true; \
    }();

	}//namespace dynamic



	//宏用于类中声明信号,并提供一个同名的方法来触发信号。示例:
	/*	void x_value_modified(int param) {
		IMPL_SIGNAL(param);
	}*/
#define IMPL_SIGNAL(...) raw_emit_signal_impl(__func__ , __VA_ARGS__)


	class QObject : public refl::dynamic::IReflectable {
	private:
		// 信号与槽的映射,键是信号名称,值是一组槽函数的信息
		std::unordered_map<std::string, std::vector<std::pair<QObject*, std::string>>> connections;
		size_t next_connection_id = 1;
		std::map<size_t, std::pair<std::string, std::pair<QObject*, std::string>>> connection_map;

	public:
		template<typename... Args>
		void raw_emit_signal_impl(const char* signal_name, Args&&... args) {
			auto it = connections.find(signal_name);
			if (it != connections.end()) {
				for (auto& slot_info : it->second) {
					slot_info.first->invoke_member_func_by_name(slot_info.second.c_str(), std::forward<Args>(args)...);
					//invoke_member_func_type_safe(*slot_info.first, slot_info.second.c_str(), std::forward<Args>(args)...); 
				}
			}
			else {
				assert(false);
			}
		}

		size_t connect(const char* signal_name, QObject* target, const char* target_member_func_name) {
			if (!target || !signal_name || !target_member_func_name) return 0;
			connections[signal_name].emplace_back(target, target_member_func_name);
			size_t id = next_connection_id++;
			connection_map[id] = { signal_name, {target, target_member_func_name} };
			return id;
		}

		template <typename SignalClass, typename SignalType, typename SlotClass, typename SlotType>
		size_t connect(SignalType SignalClass::* signal, SlotClass* slot_instance, SlotType SlotClass::* slot) {
			const char* signal_name = get_member_func_name<SignalClass>(signal);
			const char* slot_name = get_member_func_name<SlotClass>(slot);
			if (signal_name && slot_name) {
				return connect(signal_name, static_cast<QObject*>(slot_instance), slot_name);
			}
			return 0; // Failed
		}


		bool disconnect(size_t connection_id) {
			auto it = connection_map.find(connection_id);
			if (it != connection_map.end()) {
				auto& [signal_name, slot_info] = it->second;
				auto& slots = connections[signal_name];
				slots.erase(std::remove(slots.begin(), slots.end(), slot_info), slots.end());
				connection_map.erase(it);
				return true;
			}
			return false;
		}
	};

}// namespace refl


// =========================一下为使用示例代码====================================

// 用户自定义的结构体
class MyStruct :
	//public refl::dynamic::IReflectable 	// 如果不需要动态反射,可以不从public refl::dynamic::IReflectable派生
	public refl::QObject // 这里我们也测试信号槽等功能,因此从这个类派生
{

public:
	int x{ 10 };
	double y{ 20.5f };
	int print() const {
		std::cout << "MyStruct::print called! " << "x: " << x << ", y: " << y << std::endl;
		return 666;
	}
	// 如果需要支持动态调用,参数必须是std::any,并且不能超过4个参数。
	int print_with_arg(std::any param) const {
		std::cout << "MyStruct::print called! " << " arg is: " << std::any_cast<int>(param) << std::endl;
		return 888;
	}
	// 定义一个方法,用作槽函数,必须在REFLECTABLE_MENBER_FUNCS列表中,不支持返回值,并且参数必须是std::any,不能超过4个参数。
	std::any on_x_value_modified(std::any& new_value) {
		int value = std::any_cast<int>(new_value);
		std::cout << "MyStruct::on_x_value_modified called! New value is: " << value << std::endl;
		return 0;
	}


	void x_value_modified(std::any param) {
		IMPL_SIGNAL(param);
	}


	REFLECTABLE_PROPERTIES(MyStruct,
		REFLEC_PROPERTY(x),
		REFLEC_PROPERTY(y)
	);
	REFLECTABLE_MENBER_FUNCS(MyStruct,
		REFLEC_FUNCTION(print),
		REFLEC_FUNCTION(print_with_arg),
		REFLEC_FUNCTION(on_x_value_modified),
		REFLEC_FUNCTION(x_value_modified)
	);


	DECL_DYNAMIC_REFLECTABLE(MyStruct)//动态反射的支持,如果不需要动态反射,可以去掉这行代码
};

//动态反射注册类
REGEDIT_DYNAMIC_REFLECTABLE(MyStruct)


int main() {
	MyStruct obj;

	// # 静态反射部分:
	// 打印所有字段名称
	refl::For<MyStruct>::for_each_propertie_name([](const char* name) {
		std::cout << "Field name: " << name << std::endl;
		});

	// 打印所有字段值
	refl::For<MyStruct>::for_each_propertie_value(obj, [](const char* name, auto&& value) {
		std::cout << "Field " << name << " has value: " << value << std::endl;
		});

	// 打印所有函数名称
	refl::For<MyStruct>::for_each_member_func_name([](const char* name) {
		std::cout << "Member func name: " << name << std::endl;
		});

	// 获取特定成员的值,如果找不到成员,则返回默认值
	auto x_value = refl::get_field_value(obj, "x");
	std::cout << "Field x has value: " << std::any_cast<int>(x_value) << std::endl;

	auto y_value = refl::get_field_value(obj, "y");
	std::cout << "Field y has value: " << std::any_cast<double>(y_value) << std::endl;

	//修改值:
	refl::assign_field_value(obj, "y", 33.33f);
	y_value = refl::get_field_value(obj, "y");
	std::cout << "Field y has modifyed,new value is: " << std::any_cast<double>(y_value) << std::endl;

	auto z_value = refl::get_field_value(obj, "z"); // "z" 不存在
	if (z_value.type().name() == std::string_view("int")) {
		std::cout << "Field z has value: " << std::any_cast<int>(z_value) << std::endl;
	}

	// 通过字符串调用成员函数 'print'
	auto print_ret = refl::invoke_member_func_safe(obj, "print");
	std::cout << "print member return: " << std::any_cast<int>(print_ret) << std::endl;


	std::cout << "---------------------动态反射部分:" << std::endl;

	// 动态反射部分(动态反射完全不需要知道类型MyStruct的定义):
	// 动态创建 MyStruct 实例并调用方法
	auto instance = refl::dynamic::TypeRegistry::instance().create("MyStruct");
	if (instance) {
		std::cout << "Dynamic instance type: " << instance->get_type_name() << std::endl;
		// 这里可以调用 MyStruct 的成员方法
		auto x_value2 = instance->get_field_value_by_name("x");
		std::cout << "Field x has value: " << std::any_cast<int>(x_value2) << std::endl;

		instance->invoke_member_func_by_name("print");
		instance->invoke_member_func_by_name("print_with_arg", 10);
		//instance->invoke_member_func_by_name("print_with_arg", 20, 222);//这个调用会失败,命中断言,因为print_with_arg只接受一个函数
	}

	// 信号槽部分:
	std::cout << "---------------------信号槽部分:" << std::endl;

	MyStruct obj1;
	MyStruct obj2;

	// 连接obj1的信号到obj2的槽函数
	size_t connection_id = obj1.connect("x_value_modified", &obj2, "on_x_value_modified");
	if (connection_id != 0) {
		std::cout << "Signal x_value_modified from obj1 connected to on_x_value_modified slot in obj2." << std::endl;
	}

	obj1.x_value_modified(42);// 触发信号
	// 断开连接
	obj1.disconnect(connection_id);
	// 再次触发信号,应该没有任何输出,因为已经断开连接
	obj1.x_value_modified(84);

	// 使用成员函数指针版本的connect
	connection_id = obj1.connect(&MyStruct::x_value_modified, &obj2, &MyStruct::on_x_value_modified);
	if (connection_id != 0) {
		std::cout << "Signal connected to slot." << std::endl;
	}
	obj1.x_value_modified(666);// 触发信号

	return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1869399.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

吉时利 Keithley2461 数字源表

Keithley2461吉时利SMU高电流数字源表 2461 型图形化高电流数字 SourceMeter SMU 2461 高电流 SMU 凭借其 10A/1000W 脉冲电流和 7A/100W 直流电流能力以及双 18 位 1MS/s 数字转换器&#xff0c;优化用于检定和测试高功率材料、器件和模块&#xff0c;例如碳化硅 (SiC)、氮化…

WIFI各版本的带宽

带宽的定义&#xff1a; 带宽在网络领域通常指信道带宽&#xff0c;即信号在频谱中占用的频宽&#xff0c;单位是MHz&#xff08;兆赫&#xff09;。在无线通信中&#xff0c;带宽越宽&#xff0c;能够传输的数据量越大&#xff0c;因此信道带宽直接影响着数据传输速率。WiFi标…

LeetCode刷题之HOT100之岛屿数量

2024 6/27 酷暑难耐&#xff0c;天气热了&#xff0c;似乎更容易午睡了。上午上了cnn最后一节课。睡一觉买一杯蜜雪冰城&#xff0c;坐在舒适的实验室敲击键盘&#xff0c;做题&#xff01; 1、题目描述 2、逻辑分析 是的&#xff0c;又是直奔题解的一天哈&#xff01;题解给…

基于MATLAB仿真设计无线充电系统

通过学习无线充电相关课程知识&#xff0c;通过课程设计无线充电系统&#xff0c;将所学习的WPT&#xff0c;DC-DC&#xff0c;APFC进行整合得到整个无线充电系统&#xff0c;通过进行仿真研究其系统特性&#xff0c;完成我们预期系统功能和指标。 以功率器件为基本元件&#x…

鸿蒙Harmony开发案例教程:如何进行蓝牙设备发现、配对、取消配对功能

如何进行蓝牙连接 场景说明 蓝牙技术是一种无线数据和语音通信开放的全球规范&#xff0c;它是基于低成本的近距离无线连接&#xff0c;为固定和移动设备建立通信环境的一种特殊的连接。本示例通过ohos.bluetoothManager接口实现蓝牙设备发现、配对、取消配对功能。 效果呈现…

《Nest系列 - 4. 听说人人都会CRUD,可是我还不会怎么办???-《4.3结合前端使用实现多表联合查询》

&#x1f351; 联合查询 在我们前端来说&#xff0c;会抽离一些公用组件。不会把重复的组件或者所有代码都放在一个文件夹下。对于后端来说&#xff0c;也是一样的&#xff0c; 我们不会把所有数据都放在一张表里&#xff0c;我们回进行分表&#xff0c;根据一些关联关系&…

为什么说展厅数字人是展览未来的趋势?

展厅数字人是利用数字化、智能化和网络化等信息技术手段提升展厅展览服务和游览体验的全新载体。随着人工智能和虚拟现实技术的应用发展&#xff0c;展厅数字人已成为展厅展览转型升级的重要趋势。 展厅数字人凭借其创新性、强可塑性&#xff0c;成为展厅新名片&#xff0c;为各…

数据库工具之 —— SQLite

数据库工具之 —— SQLite SQLite 是一个非常流行的轻量级数据库&#xff0c;它是一个嵌入式的数据库&#xff0c;意味着数据库文件是存储在磁盘上的一个单一文件。SQLite 不需要一个独立的服务器进程&#xff0c;这使得它非常适合用于小型应用、移动应用、桌面应用&#xff0…

阿里云centos7.9 挂载数据盘 并更改宝塔站点根目录

一、让系统显示中文 参考&#xff1a;centos7 怎么让命令行显示中文&#xff08;英文-&#xff1e;中文&#xff09;_如何在命令行中显示中文-CSDN博客 1、输入命令&#xff1a;locale -a |grep "zh_CN" 可以看到已经存在了中文包 2、输入命令&#xff1a;sudo vi…

Django REST framework安全实践:轻松实现认证、权限与限流功能

系列文章目录 Django入门全攻略&#xff1a;从零搭建你的第一个Web项目Django ORM入门指南&#xff1a;从概念到实践&#xff0c;掌握模型创建、迁移与视图操作Django ORM实战&#xff1a;模型字段与元选项配置&#xff0c;以及链式过滤与QF查询详解Django ORM深度游&#xff1…

ROT5、ROT13、ROT18、ROT47全系列加解密小程序

ROT5、ROT13、ROT18、ROT47全系列加解密小程序 这几天在看CTF相关的课程&#xff0c;涉及到古典密码学和近代密码学还有现代密码学。自己编了一个关于ROT全系列的加、解密小程序。 ​ ROT5、ROT13、ROT18、ROT47 编码是一种简单的码元位置顺序替换暗码。此类编码具有可逆性&a…

use embeddings stored in vector db to reduce work for LLM generating response

题意&#xff1a;使用存储在向量数据库中的嵌入来表示&#xff0c;以减少大型语言模型&#xff08;LLM&#xff09;生成响应的工作量。 问题背景&#xff1a; Im trying to understand what the correct strategy is for storing and using embeddings in a vector database, …

计算机网路面试HTTP篇三

HTTPS RSA 握手解析 我前面讲&#xff0c;简单给大家介绍了的 HTTPS 握手过程&#xff0c;但是还不够细&#xff01; 只讲了比较基础的部分&#xff0c;所以这次我们再来深入一下 HTTPS&#xff0c;用实战抓包的方式&#xff0c;带大家再来窥探一次 HTTPS。 对于还不知道对称…

海报在线制作系统源码小程序

轻松设计&#xff0c;创意无限 一款基于ThinkPHPFastAdminUniApp开发的海报在线制作系统&#xff0c; 本系统不包含演示站中的素材模板资源。​ 一、引言&#xff1a;设计新纪元&#xff0c;在线海报制作引领潮流 在数字时代&#xff0c;海报已成为传播信息、展示创意的重要媒…

松下的台灯值得入手吗?书客、飞利浦热门品牌横评分享!

自从儿子步入小学&#xff0c;他埋首于书桌前的时光愈发冗长&#xff0c;很欣慰他能够认真专心学习&#xff0c;却也隐隐担忧他的视力健康。在了解视力健康中发现长时间在过暗或过亮的光线环境下学习&#xff0c;会导致瞳孔频繁地收缩与扩张&#xff0c;极易引发视觉疲劳。更令…

Isaac Sim 9 物理(1)

使用Python USD API 来实现 Physics 。 以下内容中&#xff0c;大部分 Python 代码可以在 Physics Python 演示脚本文件中找到&#xff0c;本文仅作为个人学习笔记。 一.设置 USD Stage 和物理场景 Setting up a USD Stage and a Physics Scene USD Stage不知道怎么翻译&#…

开放式耳机怎么选?2024全价位段机型推荐,真人实测不踩雷

作为一位深度测评耳机的专家&#xff0c;我深知对于音乐制作人员而言&#xff0c;优选一副适宜的耳机是至关重要的。我的一位朋友&#xff0c;身为音乐编辑&#xff0c;常常需长时间戴着耳机进行音频编辑与混音。他尝试了众多开放式耳机后&#xff0c;仍未找到完美契合的款式。…

使用 go-control-plane 自定义服务网格控制面

写在前面 阅读本文需要最起码了解envoy相关的概念 本文只是一个类似于demo的测试&#xff0c;只为了学习istio&#xff0c;更好的理解istio中的控制面和数据面&#xff08;pilot -> proxy&#xff09;是如何交互的&#xff0c;下图的蓝色虚线 先说go-control-plane是什么…

安宝特方案 | AR术者培养:AR眼镜如何帮助医生从“看”到“做”?

每一种新药品的上市都需要通过大量的临床试验&#xff0c;而每一种新的手术工具在普及使用之前也需要经过反复的实践和验证。医疗器械公司都面临着这样的挑战&#xff1a;如何促使保守谨慎的医生从仅仅观察新工具在手术中的应用&#xff0c;转变为在实际手术中实操这项工具。安…

简化收支记录,只留关键日期! 一键掌握财务流动,高效管理您的每一笔收支

在繁忙的生活中&#xff0c;管理个人或家庭的财务收支变得尤为重要。然而&#xff0c;传统的记账方式往往繁琐且复杂&#xff0c;让人望而却步。今天&#xff0c;我们为您推荐一款简洁易用的记账神器——晨曦记账本&#xff0c;让您轻松记录收支&#xff0c;只显示日期&#xf…