我来自python世界,作为一个周末项目,我决定用c++编写一个简单的UDP服务器。我有一个关于发现传入请求类型的正确方法的问题。我的方法是为每种可能的请求类型都创建一个类。当数据包到达时,我必须解压缩它的OPID(操作id)并实例化正确的类。要做到这一点,我必须将OPID与类绑定,而我在c++中熟悉的唯一方法是使用巨大的开关:case块。这样做对我来说并不太合适,而且如果我正确理解Uncleob,这就违背了一些OOP实践。由于代码描述了最好的意图,下面是我试图用c++做的python等价物。
class BaseOperation:
OPID = 0
def process(packet_data):
raise NotImplementedError("blah blah")
class FooOperation(BaseOperation):
OPID = 1
def process(packet_data):
print("Foo on the packet!")
class BarOperation(BaseOperation):
OPID = 2
def process(packet_data):
print("Bar on the packet!")
opid_mappings = {
FooOperation.OPID: FooOperation,
BarOperation.OPID: BarOperation
}
在处理传入数据包的代码中的某个位置
def handle_connection(packet):
try:
operation = opid_mappings[get_opid(packet)]()
except KeyError:
print("Unknown OPID")
return
operation.process(get_data(packet))
真正快速破解基于对象的解决方案。在std::function
的奇妙的新C++11世界中,这可能不是正确的做法。
如果BaseOperation的子级需要存储状态,请转到对象!
#include <iostream>
#include <map>
class BaseOperation
{
protected:
int OPID;
public:
virtual ~BaseOperation()
{
}
virtual int operator()() = 0;
};
class FooOperation:public BaseOperation
{
public:
static constexpr int OPID = 1;
FooOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int FooOperation::OPID; // allocate storage for static
class BarOperation:public BaseOperation
{
public:
static constexpr int OPID = 2;
BarOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int BarOperation::OPID; // allocate storage for static
std::map<int, BaseOperation*> opid_mappings{
{FooOperation::OPID, new FooOperation()},
{BarOperation::OPID, new BarOperation()}
};
int main()
{
std::cout << "calling OPID 1:" << (*opid_mappings[1])() << std::endl;
std::cout << "calling OPID 2:" << (*opid_mappings[2])() << std::endl;
for (std::pair<int, BaseOperation*> todel: opid_mappings)
{
delete todel.second;
}
return 0;
}
这也忽略了一个事实,即可能不需要地图。如果OPID是连续的,那么一个好的ol’dumb阵列就能解决问题。我喜欢这个映射,因为如果有人移动一个解析器处理程序或在列表中间插入一个,它就不会出错。
无论如何,这会带来一堆内存管理问题,比如for循环需要删除main
底部的解析器对象。这可以用std::unique_ptr
解决,但这可能是一个我们不需要去的兔子洞。
解析器没有任何状态的可能性非常大,我们可以只使用OPID和std::function
的映射。
#include <iostream>
#include <map>
#include <functional>
static constexpr int FooOPID = 1;
int fooOperation()
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation()
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int()>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
std::cout << "calling OPID 1:" << opid_mappings[1]() << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2]() << std::endl;
return 0;
}
因为如果你不传递任何东西,解析器就没用了,最后一个调整是:
#include <iostream>
#include <map>
#include <functional>
struct Packet
{
//whatever you need here. Probably a buffer reference and a length
};
static constexpr int FooOPID = 1;
int fooOperation(Packet & packet)
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation(Packet & packet)
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int(Packet &)>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
Packet packet;
std::cout << "calling OPID 1:" << opid_mappings[1](packet) << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2](packet) << std::endl;
return 0;
}