C++实现简化版Qt的QObject(3):增加父子关系、属性系统

前几天写了文章:
C++实现一个简单的Qt信号槽机制
C++实现简化版Qt信号槽机制(2):增加内存安全保障

之后感觉还不够过瘾,Qt中的QObject体系里还有不少功能特性没有实现。为了提高QObject的还原度,今天我们将父子关系、属性系统等功能也一并实现。

接口设计

首先,我们设计一下我们的接口。
Qt的QObject类过于重,有的时候只用到部分功能的时候没必要引入额外的成员变量。因此我们将父子关系、属性系统这两部分单独抽离成为新的类。
综合考虑下来,我们把支持静态、动态反射,信号槽的类改名为CObject,然后把功能更加完整的类命名为QObject。
在QObject补充父子关系、属性系统这两部分的接口,那么大概是这个情况:

class QObject : public CObject {
public:void setObjectName(const char* name);// 所有权机制&父子关系;void setParent(CObject* parent);CObject* findChild(const char* name);CObject* findChildRecursively(const char* name);// 属性功能:const std::any &getProperty(const char* name);template<typename T>const T &getProperty(const char* name);//方便使用的重载void setProperty(const char* name, std::any value);
};

我们这里的setParent的所有权强度和Qt的不同,在Qt中parent销毁时一定会直接销毁孩子,即使孩子外部还有强引用,因此Qt的内存管理体系可以分为两套,一套是父子关系,另一个套是QSharedPtr的共享指针。我们为了避免Qt的这种复杂度和不一致性,我们的parent销毁时,只是对孩子的引用计数-1,不一定非要直接销毁孩子。这一点是我们的QObject与Qt的不同点。

成员变量的设计

接口设计完成了,接下来我们开始成员变量的设计。

存储孩子

成员变量的设计受许多因素影响。比较麻烦的是孩子数组的选择和类型的选择,我至少考虑了以下四种的选择情况:
std::list<std::shared_ptr> children;
std::vector<std::shared_ptr> children;
std::list<std::shared_ptrrefl::dynamic::IReflectable> children;
std::vector<std::shared_ptrrefl::dynamic::IReflectable> children;

在以上四种选择情况中,我们预计 QObject 的子对象并不需要随机访问孩子,但是可能会频繁地进行插入和删除操作,因此采用std::list。至于元素类型,我们频繁需要调用refl::dynamic::IReflectable 接口提供的 share_from_this,因此选择 std::shared_ptrrefl::dynamic::IReflectable 会更加适宜,但缺点是需要在一些场景中使用dynamic_cast转换为QObject*。
因此最终选择为std::list<std::shared_ptrrefl::dynamic::IReflectable> children;。

存储属性

这个没太多可说的,直接选择:std::unordered_map<std::string, std::any> properties;

存储父亲

存储父亲指针可以有两个选择:
QObject* parent = nullptr
std::weak_ptrrefl::dynamic::IReflectable parent;
考虑弱引用语义更加准确,并且前面children也是采用IReflectable类型,因此最终选择std::weak_ptrrefl::dynamic::IReflectable parent。

代码实现

由于我们的父亲、孩子都是用了智能指针,因此我们的QObject类在析构的时候,不需要额外操作从父亲移除自己。

getProperty函数直接返回std::any,在使用上稍显繁琐,因此提供指针类型的重载模板函数,提高易用性:

template<typename T>
const T* getProperty(const char* name) {try {return &std::any_cast<const T&>(properties[name]);}catch (...) {return nullptr;}
}

最终,我们的QObject代码如下:

class QObject : public CObject {
private:std::string objectName;std::weak_ptr<refl::dynamic::IReflectable> parent;std::unordered_map<std::string, std::any> properties;std::list<std::shared_ptr<refl::dynamic::IReflectable>> children;
public:void setObjectName(const char* name) {objectName = name;}const std::string& getObjectName() {return objectName;}void setParent(QObject* newParent) {if (auto oldParent = dynamic_cast<QObject*>(parent.lock().get())) {auto it = std::find_if(oldParent->children.begin(), oldParent->children.end(),[this](const auto& child) { return child.get() == this; });if (it != oldParent->children.end()) {oldParent->children.erase(it);}}if (newParent) {parent = newParent->weak_from_this();newParent->children.push_back(shared_from_this());}else {parent.reset();}}template <typename T>void setParent(std::shared_ptr<T> newParent) {setParent(newParent.get());}void removeChild(CObject* child) {auto ch = static_cast<refl::dynamic::IReflectable*>(child);auto it = std::find_if(children.begin(), children.end(),[this, ch](const auto& child) { return child.get() == ch; });if (it != children.end()) {children.erase(it);}}CObject* findChild(const char* name) {for (auto child : children) {QObject* qChild = dynamic_cast<QObject*>(child.get());if (qChild && qChild->objectName == name) {return qChild;}}return nullptr;}CObject* findChildRecursively(const char* name) {for (auto child : children) {QObject* qChild = dynamic_cast<QObject*>(child.get());if (qChild) {if (qChild->objectName == name) {return qChild;}CObject* found = qChild->findChildRecursively(name);if (found) {return found;}}}return nullptr;}const std::any& getProperty(const char* name) {return properties[name];}template<typename T>const T* getProperty(const char* name) {try {return &std::any_cast<const T&>(properties[name]);}catch (...) {return nullptr;}}void setProperty(const char* name, const std::any& value) {properties[name] = value;}};

内存布局

一个QObject大小为264字节(x64),而一个CObject的大小为104字节。因此,开发者可以根据需要选择从CObject派生或是QObject派生。
在这里插入图片描述
在这里插入图片描述
具体的内存布局如下:
在这里插入图片描述

接下来,最后还需要提供一套异步的事件机制,才算是比较完整地实现了QObject整个体系的功能,敬请期待。

目前完整代码如下:

完整代码

#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) {// 如果函数返回空,那么兼容这种casestd::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 CObject :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::CObject* 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<CObject*>(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;}};class QObject : public CObject {private:std::string objectName;std::weak_ptr<refl::dynamic::IReflectable> parent;std::unordered_map<std::string, std::any> properties;std::list<std::shared_ptr<refl::dynamic::IReflectable>> children;public:void setObjectName(const char* name) {objectName = name;}const std::string& getObjectName() {return objectName;}void setParent(QObject* newParent) {// Detach from the current parentif (auto oldParent = dynamic_cast<QObject*>(parent.lock().get())) {auto it = std::find_if(oldParent->children.begin(), oldParent->children.end(),[this](const auto& child) { return child.get() == this; });if (it != oldParent->children.end()) {oldParent->children.erase(it);}}if (newParent) {parent = newParent->weak_from_this();newParent->children.push_back(shared_from_this());}else {parent.reset();}}template <typename T>void setParent(std::shared_ptr<T> newParent) {setParent(newParent.get());}void removeChild(CObject* child) {auto ch = static_cast<refl::dynamic::IReflectable*>(child);auto it = std::find_if(children.begin(), children.end(),[this, ch](const auto& child) { return child.get() == ch; });if (it != children.end()) {children.erase(it);}}CObject* findChild(const char* name) {for (auto child : children) {QObject* qChild = dynamic_cast<QObject*>(child.get());if (qChild && qChild->objectName == name) {return qChild;}}return nullptr;}CObject* findChildRecursively(const char* name) {for (auto child : children) {QObject* qChild = dynamic_cast<QObject*>(child.get());if (qChild) {if (qChild->objectName == name) {return qChild;}CObject* found = qChild->findChildRecursively(name);if (found) {return found;}}}return nullptr;}const std::any& getProperty(const char* name) {return properties[name];}template<typename T>const T* getProperty(const char* name) {try {return &std::any_cast<const T&>(properties[name]);}catch (...) {return nullptr;}}void setProperty(const char* name, const std::any& value) {properties[name] = value;}};}// namespace refl// =========================一下为使用示例代码====================================// 用户自定义的结构体
class MyStruct ://public refl::dynamic::IReflectable 	// 如果不需要动态反射,可以不从public refl::dynamic::IReflectable派生public refl::QObject // 这里我们也测试信号槽等功能,因此从这个类派生
{public:~MyStruct() {std::cout << getObjectName() << " destoryed " << std::endl;}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>();obj1->setObjectName("obj1");auto obj2 = std::make_shared<MyStruct>();obj2->setObjectName("obj2");obj2->setParent(obj1);// 连接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);// 使用成员函数指针版本的connectconnection_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);// 触发信号obj1.reset();std::cout << "====end=====" << std::endl;return 0;
}

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

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

相关文章

vscode远程连接linux(配置免密)

远程连接 1.首先保证物理机和虚拟机网络可以ping通 2.查看ubuntu得ip地址 ifconfig IP为&#xff1a;192.168.52.133 3.连接远程主机 配置免密 1.打开cmd运行ssh-keygen -t rsa 一路回车就行 2.打开window文件夹C:\Users\xbj\.ssh 3.用记事本打开id_rsa.pub文件复制公…

LeetCode刷题之搜索二维矩阵

2024 7/5 一如既往的晴天&#xff0c;分享几张拍的照片嘿嘿&#xff0c;好几天没做题了&#xff0c;在徘徊、踌躇、踱步。蝉鸣的有些聒噪了&#xff0c;栀子花花苞也都掉落啦&#xff0c;今天给他剪了枝&#xff0c;接回一楼来了。ok&#xff0c;做题啦&#xff01; 图1、宿舍…

数据结构之“栈”(全方位认识)

&#x1f339;个人主页&#x1f339;&#xff1a;喜欢草莓熊的bear &#x1f339;专栏&#x1f339;&#xff1a;数据结构 前言 栈是一种数据结构&#xff0c;具有" 后进先出 "的特点 或者也可见说是 ” 先进后出 “。大家一起加油吧冲冲冲&#xff01;&#xff01; …

解决vscode配置C++编译带有中文名称报错问题

在新电脑上安装vscode运行带有中文路径和中文名称的C代码时遇到报错 根据别人的教程将laugh.json文件中"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",改成了"program": "${fileDirname}\\output\\test.exe",&#x…

文化财经macd顶底背离幅图指标公式源码

DIFF:EMA(CLOSE,12) - EMA(CLOSE,26); DEA:EMA(DIFF,9); MACD:2*(DIFF-DEA),COLORSTICK; JC:CROSS(DIFF,DEA); SC:CROSSDOWN(DIFF,DEA); N1:BARSLAST(JC)1; N2:BARSLAST(SC)1; HH:VALUEWHEN(CROSSDOWN(DIFF,DEA),HHV(H,N1));//上次MACD红柱期间合约最大值 HH2:VALUEWHE…

docker部署mycat,连接上面一篇的一主二从mysql

一、docker下载mycat镜像 查看安装结果 这个名称太长&#xff0c;在安装容器时不方便操作&#xff0c;设置标签为mycat docker tag longhronshens/mycat-docker mycat 二、安装容器 先安装一个&#xff0c;主要目的是获得配置文件 docker run -it -d --name mycat -p 8066:…

nginx(三)—从Nginx配置熟悉Nginx功能

一、 Nginx配置文件结构 ... #全局块events { #events块... }http #http块 {... #http全局块server #server块{ ... #server全局块location [PATTERN] #location块{...}location [PATTERN] {...}}server{...}... #http全局块 …

怎样把自己电脑ip改成动态ip:步骤与解析

在今天的网络世界中&#xff0c;IP地址是计算机与互联网沟通的桥梁。而动态IP地址&#xff0c;作为其中的一种类型&#xff0c;由于其自动分配和管理的特性&#xff0c;为用户提供了更大的便利性和灵活性。那么&#xff0c;您是否想知道怎样将电脑IP改为动态呢&#xff1f;本文…

一级指针 二级指针

目录 一级指针 二级指针 通过二级指针打印原数据 一级指针 一级指针就是存放变量的指针 代码演示&#xff1a; #include<stdio.h> int main() {int a 10;int* pa &a;return 0; } pa就是一级指针变量&#xff0c;是变量就会有地址&#xff0c;因为变量都是在…

非堆成加密是公私钥使用

对称加密学习-CSDN博客 加密算法学习-CSDN博客 非对称加密算法使用一对密钥&#xff0c;包括一个公钥和一个私钥&#xff0c;它们是数学上相关联的&#xff0c;但公钥可以公开分享&#xff0c;而私钥必须保密。以下是使用非对称加密算法的一般步骤&#xff1a; 密钥生成&…

《昇思25天学习打卡营第13天|onereal》

今天学习的内容如下&#xff1a; DCGN生成漫画头像 在下面的教程中&#xff0c;我们将通过示例代码说明DCGAN网络如何设置网络、优化器、如何计算损失函数以及如何初始化模型权重。在本教程中&#xff0c;使用的动漫头像数据集共有70,171张动漫头像图片&#xff0c;图片大小均为…

L1218-L5298清零软件使用图解

清零前请取消打印任务&#xff0c;打印机用USB线接电脑并开启 双击[Resetter.exe]启动软件,点击[Select],选择Port打印机型号&#xff0c;然后点[OK]&#xff0c;如图。 [如果port下拉列表中找不到你的打印机&#xff0c;请更换USB接口&#xff0c;并重新开打印机重试。] 2.…

【IT领域新生必看】 Java编程中的重载(Overloading):初学者轻松掌握的全方位指南

文章目录 引言什么是方法重载&#xff08;Overloading&#xff09;&#xff1f;方法重载的基本示例 方法重载的规则1. 参数列表必须不同示例&#xff1a; 2. 返回类型可以相同也可以不同示例&#xff1a; 3. 访问修饰符可以相同也可以不同示例&#xff1a; 4. 可以抛出不同的异…

7 系列 FPGA 引脚及封装(参考ug475)

目录 I/O BankPins引脚定义I/O and Multi-Function PinsPower Supply PinsDedicated XADC PinsTransceiver PinsDedicated Configuration PinsTemperature Sensor Pins Device 视图整个 FPGAIOBILOGIC,OLOGIC,IDELAY,ODELAYBUFIO,BUFR,IDELAYCTRLBUFMRCEBRAM,DSPIBUFDS_GTE2CLB…

方法引用详解

什么是方法引用&#xff1f;&#xff1a;针对于函数式接口中的抽象方法 为什么用方法引用&#xff1f;&#xff1a;避免代码的重复&#xff0c;简便书写&#xff0c;提高效率 在使用Lambda表达式的时候&#xff0c;我们实际上传递进去的代码就是一种解决方案&#xff1a;拿参数…

警惕AI泡沫:巨额投资与回报失衡

尽管高科技巨头们在AI基础设施上投入巨资&#xff0c;但AI带来的收入增长尚未显现&#xff0c;揭示了生态系统末端用户价值的重大缺口。 红杉资本分析师David Cahn认为&#xff0c;AI企业需每年赚取约6000亿美元才能抵消其AI基础设施&#xff08;如数据中心&#xff09;的成本&…

【算法笔记自学】第 6 章 C++标准模板库(STL)介绍

6.1vector常见用法详解 #include <cstdio> #include <vector> using namespace std;int main() {int n, x;scanf("%d", &n);vector<int> v;for (int i 0; i < n; i) {scanf("%d", &x);v.push_back(x);}for (int i 0; i <…

在原有的iconfont.css文件中加入新的字体图标

前言&#xff1a;在阿里图标库中&#xff0c;如果你没有这个字体图标的线上项目&#xff0c;那么你怎么在本地项目中的原始图标文件中添加新的图标呢&#xff1f; 背景&#xff1a;现有一个vue项目&#xff0c;下面是这个前端项目的字体图标文件。现在需要新开发功能页&#x…

开发个人Go-ChatGPT--5 模型管理 (二)

开发个人Go-ChatGPT–5 模型管理 (二) ChatGPT 这是该项目的最终效果&#xff0c;使用ollama的open-webui进行人与机器的对话功能&#xff0c;对话的后端服务则完全对接自己开发的Go项目。 如何实现呢&#xff1f;则通过这篇文章&#xff0c;一一给大家剖析后端的原理及功能…

类与对像(1)

好几个月没有写了&#xff0c;差了好多&#xff0c;这些天补回来吧。 接下来&#xff0c;让我们正式步入C与C语言开始不同的地方。 我对类的理解&#xff1a;类是对于具有相同或相似属性的数据集合。 类的关键词&#xff1a;class&#xff0c;public&#xff0c;protected&a…