现在,假想需要设计一个 JSON 类型数据结构,善于提前构思的你,大致想了想:
Object { "key": Boolean, "key": Number, "key": String, "key": Array, "key": Object }
嗯,看起来没什么问题,开始设计...没写多久,可能考虑到这种情况:
Object { "key": [Object{}, Object{}, ...] }
这意味着类结构大概变成这样:
class Object { bool boolean; double number; std::string string; std::vector<Object> array; Object object; }
当然,你很明白 Object 是一个多键值对,于是改一下:
class Value { public: bool boolean; double number; std::string string; std::vector<Object> array; Object object; }; class Object { public: std::vector<std::pair<std::string key, Value value>>> objects; }
这是一个互相包含的类,对于 C++ 而言,显然编译器会报错,因为 Value 类中使用了未定义的 Object 类型,而此处编译器无法确定你的 Value 类成员变量的大小。
不过,你还是有办法,使用指针,并向前声明 Object 类:
class Object; class Value { public: bool boolean; double number; std::string string; std::vector<Object> array; Object* object; }; class Object { public: std::vector<std::pair<std::string, Value>>> objects; }
可是,这时又会有个小问题,如果你想要像下面这样使用接口
Object object; object["Window"] = Value(Object("Color", Value("blue"))); std::cout << object["Window"].GetObject()["Color"].GetString();
是不可行的,因为 Value 中 object 是指针,当重载 [] 运算符后,只能这样使用它:
Object object; object["Window"] = Value(new Object("Color", Value("blue"))); std::cout << object["Window"].GetObject()->operator[]("Color").GetString();
看起来很怪异,如果将就着用倒也没什么,但换一个位置会好些:
class Value; class Object { public: std::vector<std::pair<std::string, Value*>>> objects; } class Value { public: bool boolean; double number; std::string string; std::vector<Object> array; Object object; };
用法变为:
Object object; object["Window"] = new Value(Object("Color", Value("blue"))); std::cout << object["Window"]->GetObject()["Color"]->GetString();
如果想彻底去掉包含指针这个问题需要做一些工作,通过中间类继承 + friend class 来去掉可以做到十分完美的样子,参见微软开源的 cpprestsdk json 代码实现。
当然,或许还有其他办法解决这个问题,但我觉得最优美且最能体现 C++ 风格的方法就是继承 + 友元类。
这篇博客仅仅想写下当初写 JSON 构造 / 分析器时遇到的这个类结构设计问题,尽管这里只提到了向前类声明的使用,实际上,除开数字值的类型问题(int64 -> int32 -> double 溢出问题),性能问题,当时还学到了很多 C++ 的用法。(以后可能会较少用 C++ 了)
最后给一个极简的 JSON 构造器实现:
#include <iostream> #include <vector> #include <string> class Value; class Boolean { private: bool _boolean; public: Boolean() : _boolean() {} Boolean(bool val) : _boolean(val) { } bool GetVal() const { return _boolean; } void SetVal(bool val) { _boolean = val; } }; class Number { private: double _number; public: Number() : _number() {} Number(int val) : _number(val) { } Number(double val) : _number(val) { } double GetVal() const { return _number; } void SetVal(double val) { _number = val; } }; class String { private: std::string _string; public: String() {} String(const char* val) : _string(val) { } String(std::string val) : _string(val) { } std::string GetVal() const { return _string; } void SetVal(std::string val) { _string = val; } }; class Array { private: std::vector<Value> _array; public: Array() : _array() { } Array(std::vector<Value> val) : _array(val) { } std::vector<Value> GetVal() const { return _array; } void SetVal(std::vector<Value> val) { _array = val; } }; class Object { std::vector<std::pair<std::string, Value*>> _elements; private: typedef std::vector<std::pair<std::string, Value*>>::iterator iterator; iterator find_iter_by_key(std::string key) { return std::find_if(_elements.begin(), _elements.end(), [&key](const std::pair<std::string, Value*>& p) { return p.first == key; }); } public: Object() {} Object(std::string key, Value* value) : _elements{ {key, value} } { } Value*& operator[](std::string key) { return GetValue(key); } size_t Size() const { return _elements.size(); } Value*& GetValue(std::string key) { auto iter = find_iter_by_key(key); if (iter == _elements.end()) return _elements.insert(iter, std::pair<std::string, Value*>(key, nullptr))->second; return iter->second; } }; class Value { Boolean v_boolean; Number v_number; String v_string; Array v_array; Object v_object; public: Value() : v_boolean(), v_number(), v_string(), v_array(), v_object() { } Value(const Value & value) { *this = value; } Value(bool value) : v_boolean(value), v_number(), v_string(), v_array(), v_object() { } Value(int value) : v_boolean(), v_number(value), v_string(), v_array(), v_object() { } Value(double value) : v_boolean(), v_number(value), v_string(), v_array(), v_object() { } Value(std::string value) : v_boolean(), v_number(), v_string(value), v_array(), v_object() { } Value(std::vector<Value> value) : v_boolean(), v_number(), v_string(), v_array(value), v_object() { } Value(Boolean value) : v_boolean(value), v_number(), v_string(), v_array(), v_object() { } Value(Number value) : v_boolean(), v_number(value), v_string(), v_array(), v_object() { } Value(String value) : v_boolean(), v_number(), v_string(value), v_array(), v_object() { } Value(Array value) : v_boolean(), v_number(), v_string(), v_array(value), v_object() { } Value(Object value) : v_boolean(), v_number(), v_string(), v_array(), v_object(value) { } bool GetBoolean() const { return v_boolean.GetVal(); } double GetNumber() const { return v_number.GetVal(); } int GetIntNumber() const { return (int)v_number.GetVal(); } std::string GetString() const { return v_string.GetVal(); } std::vector<Value> GetArray() const { return v_array.GetVal(); } Object GetObject() const { return v_object; } Value& GetValue() { return *this; } }; int main() { Object object("test", new Value(Object("abc", new Value(123)))); object["color"] = new Value(Array{ std::vector<Value>{ Value(String("red")), Value(String("orange")), Value(String("yellow")) } }); std::cout << object["test"]->GetObject()["abc"]->GetIntNumber() << ' '; for (auto i : object["color"]->GetArray()) std::cout << i.GetString() << " "; return 0; }
输出:
123 red orange yellow