现在,假想需要设计一个 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