C++实现简化版Qt信号槽机制(2):增加内存安全保障

在上一篇文章中《C++实现一个简单的Qt信号槽机制》,我们基于前面的反射代码实现了信号槽的功能。
但是上一篇的代码中没有对象生命周期管理的机制,如果在对象的生命周期结束后还存在未断开的信号和槽连接,那么信号触发时可能会尝试访问已经被析构的对象,从而引发内存访问异常。这个设计缺陷在C++越来越

方案的确定

在Qt框架中,QObject的析构函数,在对象销毁时会自动断开所有信号和槽的连接。从而避免出现这种情况。
笔者考虑了一下,没有采取这种方案,因为:

  • 这会要求信号和槽两个对象互相存储双方的连接情况,在一定程度上浪费了内存。
  • 另一方面,我们还没有支持对象的内存管理,没有弱引用机制。如果通过析构函数反连接的做法那么还需要单独再次引入弱引用机制。

经过一轮思索,笔者选择的方案是:

  • 引入弱引用机制,解决内存管理问题
  • 在信号触发时,自动跳过已经析构的槽对象,并清理无效的槽链接。

实现

弱引用机制

C++标准库本身支持一套内存管理机制,那就是std::shared_ptr体系。这套体系也支持弱指针引用,我们尽可能避免重复造轮子,因此我们直接采用这种机制。
综合考虑下来,我们决定让我们的的IReflectable 从std::enable_shared_from_this派生。(之所以不是QObject从shared_ptr派生,是因为IReflectable 有方法工厂,我们希望方法工厂返回的IReflectable 实例就是被shared_ptr承载的)

class IReflectable : public std::enable_shared_from_this<IReflectable> {
		//...
		}

然后,在信号触发的时候,判断对象是否有效:

		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) {
					auto ptr = std::get<0>(slot_info).lock();// 获取弱引用的有效指针
					if (ptr) {
						ptr->invoke_member_func_by_name(std::get<1>(slot_info).c_str(), std::forward<Args>(args)...);
					}// else 对象已经析构了
				}
			}
			else {
				assert(false);
			}
		}

之所以没有在else那里清理掉已经无效的链接,是因为在信号槽连接的时候,外部可能保存了数组的迭代器:
[外链图片转存中…(img-jIOqrBqD-1719542815307)]
直接返回迭代器的好处是在disconnect的时候性能特别好,并且实现简单:


		template <typename T>
		bool disconnect(T connection) {
			//T是个这个类型:std::make_optional(std::make_tuple(this, itMap, it));
			//由于T过于复杂,就直接用模板算了
			if (!connection) {
				return false;
			}
			auto& tuple = connection.value();
			if (std::get<0>(tuple) != this) {
				return false;//不是我的connection呀
			}
			std::get<1>(tuple)->second.erase(std::get<2>(tuple));
			return true;
		}

缺点是连接的信号一旦移除之后其他迭代器会失效。这样做肯定不行。于是把迭代器的数组类型从vector换成了list。避免迭代器失效,同时保留了connect返回迭代器的优势,同时支持在信号触发的时候直接清理掉已经析构的槽对象,避免死对象无法清除。一举N得。
于是只需要修改这一行即可:

using connections_list_type = std::vector<std::tuple< std::weak_ptr<QObject>, std::string>>;

改为:

using connections_list_type = std::list<std::tuple< std::weak_ptr<QObject>, std::string>>;

其他地方都不用修改,这就是C++ auto 的力量。

接下来,把前面raw_emit_signal_impl函数中对象无效后从list清理的逻辑补上,大功告成。
实现的时候,考虑性能问题,只在发现存在无效对象时才进行批量移除,因此引入局部变量has_invalid_slot 来标识这种情况。代码如下:

		template<typename... Args>
		void raw_emit_signal_impl(const char* signal_name, Args&&... args) {
			auto it = connections.find(signal_name);
			if (it != connections.end()) {
				auto& slots = it->second; // 获取槽信息列表的引用
				bool has_invalid_slot = false;
				for (const auto& slot_info : slots) {
					auto ptr = std::get<0>(slot_info).lock(); // 锁定弱引用
					if (ptr) {
						ptr->invoke_member_func_by_name(std::get<1>(slot_info).c_str(), std::forward<Args>(args)...);
					}
					else {
						has_invalid_slot = true;
					}
				}
				if (has_invalid_slot) {
					//如果存在无效对象,则执行一轮移除操作
					auto remove_it = std::remove_if(slots.begin(), slots.end(),
						[](const auto& slot_info) {
							return std::get<0>(slot_info).expired(); // 检查弱引用是否失效
						});
					slots.erase(remove_it, slots.end());
				}
			}
			else {/*没找到这个信号,要不要assert?*/ }
		}

解决要求所有的QObject派生对象都需要通过make_shared

由于使用了C++的shared_ptr,因此我们的QObject派生对象都需要通过make_shared创建,否则成员函数make_weeked和make_shared都是无效的。
为了避免使用者用错,我们connect的时候,会对weak_ptr是否有效进行检查,这样就能确保我们的内存管理机制符合预期,规避开发者误用了。
在这里插入图片描述

那么,我们一系列的函数接口都要进行优化,使其与share_ptr类型更好的适配,避免开发者大量编写ptr.get()等干扰可读性的代码。
我们以connect函数为例,提供两个重载,以直接支持shared_ptr:

template <typename SlotClass>
auto connect(const char* signal_name, std::shared_ptr<SlotClass> slot_instance, const char* slot_member_func_name) {
	return connect(signal_name, slot_instance.get(), slot_member_func_name);
}
template <typename SignalClass, typename SignalType, typename SlotClass, typename SlotType>
auto connect(SignalType SignalClass::* signal, std::shared_ptr<SlotClass> &slot_instance, SlotType SlotClass::* slot) {
	return connect(signal, slot_instance.get(), slot);
}

这样,使用者就可以保持代码的简洁性了。

不过,在适配其他函数的时候,笔者还是有点纠结:

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

改为

template <typename T, size_t N = 0>
std::any get_field_value(T* obj, const char* name) {
	return obj ? __get_field_value_impl(*obj, name, T::properties()) : std::any();
}

就需要对obj进行判空。因为指针的语义是可空,而引用的语义是非空,语义宽松了,也增加了一种新的错误类型(第一种错误是找不到,新增了对象为空的错误),新引入了一个新的错误这个点确实让我有点纠结,但又没有什么好办法,只能说鱼和熊掌不能兼得呀。

最终,测试代码里MyStruct obj;都变成make_shared即可:auto obj = std::make_shared();

这一轮优化后,完整代码如下:

#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 obj ? __get_field_value_impl(*obj, name, T::properties()) : std::any();
	}

	// 使用 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 obj ? __assign_field_value_impl(*obj, name, value, T::properties()) : std::any();
	}

	// 成员函数调用相关:
	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 obj ? __invoke_member_func_impl(obj, name, funcs, std::forward<Args>(args)...) : std::any();
	}

	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 obj ? __invoke_member_func_impl<true>(obj, name, funcs, std::forward<Args>(args)...) : std::any();
	}

	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 std::enable_shared_from_this<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::shared_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::shared_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::shared_ptr<::refl::dynamic::IReflectable> create_instance() { return std::make_shared<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 REFLEC_IMPL_SIGNAL(...) raw_emit_signal_impl(__func__ , __VA_ARGS__)


	class QObject :
		public refl::dynamic::IReflectable {
	private:
		// 信号与槽的映射,键是信号名称,值是一组槽函数的信息
		using connections_list_type = std::list<std::tuple< std::weak_ptr<IReflectable>, std::string>>;
		using connections_type = std::unordered_map<std::string, connections_list_type>;
		connections_type connections;

	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()) {
				auto& slots = it->second; // 获取槽信息列表的引用
				bool has_invalid_slot = false;
				for (const auto& slot_info : slots) {
					auto ptr = std::get<0>(slot_info).lock(); // 锁定弱引用
					if (ptr) {
						ptr->invoke_member_func_by_name(std::get<1>(slot_info).c_str(), std::forward<Args>(args)...);
					}
					else {
						has_invalid_slot = true;
					}
				}
				if (has_invalid_slot) {
					//如果存在无效对象,则执行一轮移除操作
					auto remove_it = std::remove_if(slots.begin(), slots.end(),
						[](const auto& slot_info) {
							return std::get<0>(slot_info).expired(); // 检查弱引用是否失效
						});
					slots.erase(remove_it, slots.end());
				}
			}
			else {/*没找到这个信号,要不要assert?*/ }
		}

		auto connect(const char* signal_name, refl::QObject* slot_instance, const char* slot_member_func_name) {
			if (!slot_instance || !signal_name || !slot_member_func_name) {
				throw std::runtime_error("param is null!");
			}
			assert(slot_instance->weak_from_this().lock());//target必须通过make_share构造!!因为要弱引用它

			std::string str_signal_name(signal_name);
			auto itMap = connections.find(str_signal_name);
			if (itMap != connections.end()) {
				itMap->second.emplace_back(slot_instance->weak_from_this(), slot_member_func_name);//必须插入末尾,因为返回了--end()迭代器指示这个链接
				return std::make_optional(std::make_tuple(this, itMap, --itMap->second.end()));
			}
			else {
				// 如果没找到,插入新元素到map中,并获取迭代器
				auto emplace_result = connections.emplace(std::make_pair(std::move(str_signal_name), connections_list_type()));
				itMap = emplace_result.first;
				itMap->second.emplace_back(slot_instance->weak_from_this(), slot_member_func_name);
				return std::make_optional(std::make_tuple(this, itMap, --itMap->second.end()));
			}
		}
		template <typename SlotClass>
		auto connect(const char* signal_name, std::shared_ptr<SlotClass> slot_instance, const char* slot_member_func_name) {
			return connect(signal_name, slot_instance.get(), slot_member_func_name);
		}

		template <typename SignalClass, typename SignalType, typename SlotClass, typename SlotType>
		auto 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);
			}
			throw std::runtime_error("signal name or slot_name is not found!");
		}
		template <typename SignalClass, typename SignalType, typename SlotClass, typename SlotType>
		auto connect(SignalType SignalClass::* signal, std::shared_ptr<SlotClass>& slot_instance, SlotType SlotClass::* slot) {
			return connect(signal, slot_instance.get(), slot);
		}

		template <typename T>
		bool disconnect(T connection) {
			//T是个这个类型:std::make_optional(std::make_tuple(this, itMap, it)); 由于T过于复杂,就直接用模板算了
			if (!connection) {
				return false;
			}
			auto& tuple = connection.value();
			if (std::get<0>(tuple) != this) {
				return false;//不是我的connection呀
			}
			std::get<1>(tuple)->second.erase(std::get<2>(tuple));
			return true;
		}
	};

}// 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) {
		REFLEC_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() {
	auto obj = std::make_shared<MyStruct>();

	// # 静态反射部分:
	// 打印所有字段名称
	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.get(), [](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.get(), "x");
	std::cout << "Field x has value: " << std::any_cast<int>(x_value) << std::endl;

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

	//修改值:
	refl::assign_field_value(obj.get(), "y", 33.33f);
	y_value = refl::get_field_value(obj.get(), "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.get(), "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.get(), "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;

	auto obj1 = std::make_shared<MyStruct>();
	auto obj2 = std::make_shared<MyStruct>();

	// 连接obj1的信号到obj2的槽函数
	auto connection_id = obj1->connect("x_value_modified", obj2.get(), "on_x_value_modified");
	if (!connection_id) {
		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) {
		std::cout << "Signal connected to slot." << std::endl;
	}
	obj1->x_value_modified(666);// 触发信号

	return 0;
}


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis 7.x 系列【11】数据类型之位图(Bitmap)

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 基本命令2.1 SETBIT2.2 GETBIT2.3 BITCOUNT2.4 BITPOS2.5 BITFIELD2.6 BITF…

【产品经理】订单处理10-分配快递策略

本次主要讲解下在订单处理过程中分配快递的策略以及分配快递中需要用到的设置。 一、建立快递档案 在ERP系统中&#xff0c;需要建立快递档案&#xff0c;设置所属快递、快递的服务类型、支持的打印模版以及快递在各个平台的电子面单支持情况。 二、仓库绑定快递 仓库需要设…

【动态规划】279.完全平方数

279. 完全平方数 难度&#xff1a;中等 力扣地址&#xff1a;https://leetcode.cn/problems/perfect-squares/ 没有刷过的小伙伴们请一定要先去刷一次&#xff0c;然后如果感兴趣的话再阅读以下内容&#xff0c;便于交流 ~ 多谢支持 ~ 问题描述 给你一个整数 n &#xff0c;返…

C# 入门—基本语法

一、数据类型 C# 语言中内置了一些基本的数据类型&#xff0c;数据类型用来指定程序中变量可以存储的数据的类型&#xff0c;C# 中的数据类型可以大致分为三类&#xff1a; 值类型&#xff08;Value types&#xff09;&#xff1b;引用类型&#xff08;References types&…

[推荐]有安全一点的网贷大数据信用查询网站吗?

在互联网金融日益发展的今天&#xff0c;网贷大数据查询网站成为了许多人申贷前的必备工具。随着使用这些网站的人群越来越多&#xff0c;安全问题也逐渐浮出水面。最近&#xff0c;就有许多用户反馈自己的个人信息在网贷大数据查询网站上被泄露。为了解决这一问题&#xff0c;…

Spring Cloud Gateway3.x自定义Spring Cloud Loadbalancer负载均衡策略以及实现动态负载均衡策略的方案

目录 前言 1.原理分析 1.1 ReactiveLoadBalancerClientFilter源码分析 1.2 LoadBalancerClientFactory源码分析 2.代码实现 2.1 扩展原生RoundRobinLoadBalancer轮询策略 2.1.1 自定义实现RoundRobinLoadBalancer 2.1.2 配置自定义的RoundRobinLoadBalan…

idea2024使用springboot3.x系列新建java项目,使用jdk17,启动项目报错

身为一名开发人员&#xff0c;敲代码无数&#xff0c;竟被一个小小启动给我卡了大半天&#xff0c;太丢脸了 报错一&#xff1a;Field infoSysRepository in com.erectile.Impl.PersonalInfoServiceImpl required a bean of type ‘com.erectile.jpa.repository.InfoSysReposit…

前端vue使用onlyoffice控件实现word在线编辑、预览(仅列出前端部分需要做的工作,不包含后端部分)

简介 ONLYOFFICE 文档 是一个开源办公套件&#xff0c;包括文本文档、电子表格、演示文稿和可填写表单的编辑器。 它提供以下功能&#xff1a; 创建、编辑和查看文本文档、电子表格、演示文稿和可填写表单&#xff1b; 与其他队友实时协作处理文件。 基于这个控件&#xff0c;…

微信小程序根据蓝牙RSSI信号强度测试设备距离

背景 在做小程序连接蓝牙设备的时候&#xff0c;有需求表明在搜索到0.5米之内的设备时自动连接 问题&#xff1a; 蓝牙模组只提供了RSSI信号强度&#xff0c;那又该如何计算蓝牙设备距离小程序的距离呢&#xff1f; 解决方案 通过以下公式做大量测试&#xff1a;求 A、n 的平均…

【深度学习】卷积神经网络CNN

李宏毅深度学习笔记 图像分类 图像可以描述为三维张量&#xff08;张量可以想成维度大于 2 的矩阵&#xff09;。一张图像是一个三维的张量&#xff0c;其中一维代表图像的宽&#xff0c;另外一维代表图像的高&#xff0c;还有一维代表图像的通道&#xff08;channel&#xff…

【LeetCode】四、栈相关:有效的括号 + 下一个更大的元素

文章目录 1、栈结构2、Java中的栈3、leetcode20&#xff1a;有效的括号4、leetcode496&#xff1a;下一个更大元素 1、栈结构 和队列相反&#xff0c;栈先进后出 时间复杂度&#xff1a;访问、插入、删除都在栈顶进行操作&#xff0c;时间复杂度为O(1)&#xff0c;搜索需要遍…

技术分享:分布式数据库DNS服务器的架构思路

DNS是企业数字化转型的基石。伴随微服务或单元化部署的推广&#xff0c;许多用户也开始采用分布式数据库将原来的单体数据库集群服务架构拆分为大量分布式子服务集群&#xff0c;对应不同的微服务或服务单元。本文将从分布式数据库DNS服务器的架构需求、架构分析两方面入手&…

2.用BGP对等体发送路由

2.用BGP对等体发送路由 实验拓扑&#xff1a; 实验要求&#xff1a;用BGP对等体发送路由信息 实验步骤&#xff1a; 1.完成基本配置&#xff08;略&#xff09; 2.建立BGP对等体&#xff08;略&#xff09; 3.创建路由信息&#xff08;用创建一个loop back接口就能产生一个直连…

【java】【控制台】【javaSE】 初级java家教管理系统控制台命令行程序项目

更多项目点击&#x1f446;&#x1f446;&#x1f446;完整项目成品专栏 【java】【控制台】【javaSE】 初级java家教管理系统控制台命令行程序项目 获取源码方式项目说明&#xff1a;功能点数据库涉及到&#xff1a; 项目文件包含&#xff1a;项目运行环境 &#xff1a;截图其…

HarmonyOS Next开发学习手册——弹性布局 (Flex)

概述 弹性布局&#xff08; Flex &#xff09;提供更加有效的方式对容器中的子元素进行排列、对齐和分配剩余空间。常用于页面头部导航栏的均匀分布、页面框架的搭建、多行数据的排列等。 容器默认存在主轴与交叉轴&#xff0c;子元素默认沿主轴排列&#xff0c;子元素在主轴…

网络流-EK算法(保姆级教学)

本文引用董晓算法的部分图片。 一些不能带入纸质资料的竞赛&#xff0c;网络流纳入考纲。 因为需要默写&#xff0c;想来也不会考默写dinic这种算法难倒大家&#xff0c;只需要快速敲对EK算法就行了。 EK算法能在O(n*m^2)的复杂度内解决最大流问题&#xff0c;其中最大流就是…

Flutter循序渐进==>封装、继承、多态、抽象类以及属性修改

导言 新学一门编程语言&#xff0c;最难以理解的莫过于类了。如果类没用&#xff0c;也就算了&#xff0c;它偏偏很有用&#xff0c;我们必须得掌握&#xff0c;不然怎么好意思说自己会面向对象编程呢? 抽象类&#xff08;Abstract Class&#xff09;在面向对象编程中扮演着…

如何看待AIGC中漫画版权争议?( 计育韬老师高校公益巡讲答疑实录2024)

这是计育韬老师第 8 次开展面向全国高校的新媒体技术公益巡讲活动了。而在每场讲座尾声&#xff0c;互动答疑环节往往反映了高校师生当前最普遍的运营困境&#xff0c;特此计老师在现场即兴答疑之外&#xff0c;会尽量选择有较高价值的提问进行文字答疑梳理。 *本轮巡讲主题除了…

java 操作 milvus 2.1.4

1. 确认 docker 运行的 milvus容器镜像版本情况&#xff1a; 2. pom 依赖&#xff1a; <dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><version>2.1.0</version><exclusions><exclusi…

【秋招突围】2024届秋招笔试-科大笔试题-01-三语言题解(Java/Cpp/Python)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系计划跟新各公司春秋招的笔试题 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; 文章目录 &#x1f4d6…