前几天写了文章:
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