我想用更优雅的东西代替大开关。
class Base
{
public:
Base(void*data, int size);
virtual void Something() = 0;
}
class A : public Base
{
public:
A(void*data, int size) : Base(data, size) {}
void Something() override;
}
class B : public Base
{
public:
B(void*data, int size) : Base(data, size) {}
void Something() override;
}
...
{
char c = input;
switch (c)
{
case 'a':
{
A obj(data, size);
obj.Something();
break;
}
case 'b':
{
B obj(data, size);
obj.Something();
break;
}
...
}
}
在示例类A
和B
中看到的与外部没有区别。 我想找到一种消除实例化正确类并在其上调用相同方法的开关的方法,在我的真实代码中,该开关的长度超过1000行,并且有更多的类,但是我找不到任何方法摆脱它。
在实际代码中,我有2个枚举而不是char,并且有更多的开关,但我希望问题清楚。
我的想法之一是在基类上使用模板,但是我没有找到一种方法来在没有巨大切换的情况下实例化正确的孩子。
编辑
我收到了来自网络的数据包,并希望对其进行解析和处理。这些类a, b, ...
没有任何私有或公共成员,基类仅具有指向日期的原始指针,并且具有指向响应套接字的共享指针(同样来自构造函数)。
我希望编译器使用 templates 或其他某种机制来为我生成该开关,以删除重复的源代码。现在它仍处于测试阶段,但是它每秒处理大约1000个数据包,因此我不想在堆的分配和释放上删除交换机并降低性能。
答案 0 :(得分:3)
找到static_for
的实现,它本身很简单:
using list = std::tuple<A, B, C, D, E, F, G, ...>;
const auto n = c - 'a';
static_for<std::tuple_size<list>()>([&](auto N){
if (n != N)
return;
using T = std::tuple_element_t<list, N>;
T obj(data, size);
obj.Something();
});
更多注意事项:
如果它们都具有相同的多态接口,则可以决定仅使用此接口来创建对象。
如果您的范围内有漏洞,则if constexpr
和std::is_same
是您的朋友。
使用一些专用的typelist-type而不是std::tuple
可能会更好,但这在紧要关头有效。
static_for()
的粗糙,快速且肮脏的示例实现:
template <std::size_t Is, class F>
void static_for_impl(F&& f, std::index_sequence<Is...>) {
f(std::integral_constant<std::size_t, Is>()), ...;
}
template <std::size_t N, class F>
void static_for(F&& f) {
static_for_impl(f, std::make_index_sequence<N>());
}
答案 1 :(得分:1)
类似的事情应该起作用:
#include <map>
#include <functional>
#include <memory>
typedef std::function< std::unique_ptr< Base >( void* data, int size ) > Factory;
std::map< char, Factory > factories =
{
{ 'a', []( void* data, int size ){ return std::make_unique<A>( data, size ); } },
{ 'b', []( void* data, int size ){ return std::make_unique<B>( data, size ); } }
};
char input = 'a';
void* data = 0;
int size = 0;
auto factory = factories.find( input );
if ( factory != factories.end() )
{
factory->second( data, size )->Something();
}
您只需要在每个类的工厂列表中添加一行。
如果您使用的枚举具有从0开始的连续值,那么您可以使用数组而不是std::map
,例如:
enum class Class
{
a,
b
};
Factory factories[] =
{
[]( void* data, int size ){ return std::make_unique<A>( data, size ); },
[]( void* data, int size ){ return std::make_unique<B>( data, size ); }
};
Class input = Class::a;
factories[static_cast<size_t>(input)]( data, size )->Something();
答案 2 :(得分:1)
如果构造函数完全相同,并且function writeFile(fileEntry, dataObj) {
// Create a FileWriter object for our FileEntry (log.txt).
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
readFile(fileEntry);
};
fileWriter.onerror = function (e) {
console.log("Failed file write: " + e.toString());
};
// If data object is not passed in,
// create a new Blob instead.
if (!dataObj) {
dataObj = new Blob(['some file data'], { type: 'text/plain' });
}
fileWriter.write(dataObj);
});
方法的调用类似,那么您应该能够使用如下模板:
Something
如果要验证模板是template<typename T>
void DoSomething(void*data, int size){
T t(data, size);
t.Something();
}
..
{
switch(input){
case 'a': DoSomething<A>(..); break;
case 'b': DoSomething<B>(..); break;
}
}
的派生类,可以使用is_base_of
。
由于您打开了一个未知变量(在这种情况下为Base
),所以我不确定如何最小化该开关,除非遵循模式suggested by Alan Birtles。
答案 3 :(得分:1)
您可以使用简单的工厂方法通过所需的类型和构造函数参数来创建对象,如以下示例所示。使用继承和虚拟函数时,不要忘记虚拟析构函数。
#include <memory>
class Base
{
public:
Base(void* data, int size) {};
virtual ~Base() {}
virtual void Something() = 0;
};
class A : public Base
{
public:
A(void* data, int size) : Base(data, size) {}
void Something() override {};
};
class B : public Base
{
public:
B(void* data, int size) : Base(data, size) {}
void Something() override {};
};
Base* MyFactory(char type, void* data, int size)
{
switch (type)
{
case 'a': return new A(data, size);
case 'b': return new B(data, size);
default:
return nullptr;
}
}
int main()
{
std::unique_ptr<Base> obj1(MyFactory('a', nullptr, 1));
obj1->Something();
std::unique_ptr<Base> obj2(MyFactory('b', nullptr, 1));
obj2->Something();
}