使用指向成员的指针数组还是使用开关更好
Is it better to use an array of pointers to members or a switch?
在我的学校里,我们强烈鼓励使用指向成员的指针数组,而不是在C++(和C)中切换(或多个else if)。
由于我认为使用这样的数组(我实际上使用指向成员的指针映射)而不是switch语句没有任何意义,我想知道是否真的有任何优化可以推荐指向函数的指针。
以下是让我认为最好使用switch:的原因
-
指向成员的指针数组(尤其是映射)内存很大(std::string作为键,指针作为值),需要存储在类中(没有任何意义,因为它不是对象属性…),或者每次使用它们在函数中重新创建(如果静态声明):
std::map<std::string, void (MyClass::*)(...)> operations;
-
它们很难实例化并准备使用:
operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("push", &Parser::push)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("pop", &Parser::pop)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("dump", &Parser::dump)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("assert", &Parser::assert)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("add", &Parser::add)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("sub", &Parser::sub)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("mul", &Parser::mul)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("div", &Parser::div)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("mod", &Parser::pop)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("print", &Parser::print)); operations.insert(std::map<std::string, void (Parser::*)(std::vector<std::string> const &)>::value_type("exit", &Parser::exit));
-
它迫使您在某些函数中使用无用的参数,并使用可能是const的非常数成员。例如,在我之前的一段代码中,如果映射中没有使用"print"answers"assert",它们可能是const,而且大多数函数都没有使用参数,但"push"answers"断言"是。。。
-
你必须验证你想要使用的指针是否存在于映射中,而不是让"默认"情况来处理它,并且调用很难读取:
if (operations.find(myOperation) != operations.end()) (this->*(operations.find(myOperation)->second))(myParameter);
那么,为什么我们被迫使用指向成员的指针,而不仅仅是一个clear switch语句,甚至是else if?
谢谢。
这取决于情况。具有多个未连接选项的切换情况实际上与大if-else
-慢速相同。好的优化是使用偏移表(或跳转表)执行所需的操作,建议您执行该操作。
奇怪的是,如果switch-case
写得好,编译器通常可以自动执行这种优化。
但是写得好意味着什么?
这意味着,您必须设计条目索引,这样就可以轻松快速地计算需要执行的条目的位置。考虑以下代码:
int n = 0;
std::cin >> n;
if(n == 1) printf("1n");
else if(n == 2) printf("2n");
else if(n == 3) printf("3n");
else if(n == 4) printf("4n");
这是可能的输出(VC11上的实际输出,用/O2编译):
011AA799 mov eax,dword ptr [n]
011AA79C cmp eax,1 //is n equal to 1?
011AA79F jne main+34h (011AA7B4h) //if yes, continue, if not, jump... [J1]
011AA7A1 push 1262658h
011AA7A6 call printf (011E1540h) // print 1
011AA7AB add esp,4
011AA7AE xor eax,eax
011AA7B0 mov esp,ebp
011AA7B2 pop ebp
011AA7B3 ret
011AA7B4 cmp eax,2 // [J1] ...here. Is n equal to 2?
011AA7B7 jne main+4Ch (011AA7CCh) //If yes, continue, if not, jump... [J2]
011AA7B9 push 126265Ch
011AA7BE call printf (011E1540h) // print 2
011AA7C3 add esp,4
011AA7C6 xor eax,eax
011AA7C8 mov esp,ebp
011AA7CA pop ebp
011AA7CB ret
011AA7CC cmp eax,3 // [J2] ...here. Is n equal to 3? (and so on...)
011AA7CF jne main+64h (011AA7E4h)
011AA7D1 push 1262660h
011AA7D6 call printf (011E1540h)
[...]
基本上是if-else
。现在,让我们更改代码:
int n = 0;
std::cin >> n;
switch(n)
{
case 1: printf("1n"); break;
case 2: printf("2n"); break;
case 3: printf("3n"); break;
case 4: printf("4n"); break;
}
可能输出:
011BA799 mov eax,dword ptr [n] // switch case will run if n is 1-4
011BA79C dec eax //decrement by one, now it should be in 0-3
011BA79D cmp eax,3 // compare with 3
011BA7A0 ja $LN4+46h (011BA7EFh) //if greater than 3, skip switch
011BA7A2 jmp dword ptr [eax*4+11BA7F8h] //otherwise compute offset of instrcution and jump there
我没有发布对printf
的调用——本质上是一样的,但没有任何cmp
或跳转指令。
当然,这个输出只是众多可能的输出之一,但关键是:设计良好的应用程序,对条件部分进行智能优化,可以执行更高效的。在这里,编译器能够直接跳转到正确的指令,因为它可以很容易地计算它的偏移量——所有情况都用数字标记,数字增长一。
为了更直接地回答你的问题:给你的建议在技术上是正确的,但我会专注于编译器友好的优化,而不是复杂的代码(这可能会也可能不会显著提高速度),这是每个人都能理解和依赖的(只要编译器足够聪明,能够利用这一优势生成优化的代码)。
与切换指令相比,您对指向成员函数的指针数组的优缺点的分析已经非常好了。
但这一切都取决于上下文:
-
当然,从技术上讲,你是完全正确的:如果你只想更换一个交换机,这样的阵列会非常麻烦。更不用说编译器,他们可以通过使用比数组少使用一个间接寻址的跳表来优化开关。
-
但是您的示例代码实现了一种命令设计模式。从设计的角度来看,这可能在进化性和可维护性方面有很大的优势,超过了技术缺陷。例如,它可以很容易地在应用程序中用于实现撤消/重做功能。它还简化了几个同时使用的用户界面允许在对象上触发这些命令的情况(例如:命令行窗口和GUI)
上下文很重要。
如果你用电脑工作,我认为更喜欢数组,因为与非常快速的比较相比,获得结果非常快,这是用内存支付的。这在内存方面很昂贵,但在阵列非常大的情况下速度很快。
如果上下文是一个微控制器,那么内存非常昂贵,并且不能浪费来保存所有数组。尤其是在几乎不使用阵列的情况下。但是,由于不使用内存,而且汇编程序在微控制器中速度非常快,因此可以首选开关。
- 如果你有这么多内存,并且是一种高级编程语言,那么数组可能会更好
- 如果您的内存太少,并且使用汇编程序或微型计算机中的C等低级编程语言,那么最好使用switch(或rom表)
- C++:将控制台输出存储在宏中更好吗
- FFmpeg:制作一个应用程序比直接使用ffmepg更好吗
- 初始化具有非默认构造函数的std::数组项的更好方法
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 为什么新的随机库比std::rand()更好
- 寻找一种更好的方法来表示无符号字符数组
- 哪种方法更好,性能明智
- 什么更好?返回对象指针列表?或返回指向对象列表的指针?
- 什么是更好的做法?通过指针或标识符传递类成员?
- 寻求更好地理解标准::访问
- 线程消息传递或更好:在"大师班"中访问其他班级的成员
- 有没有更好的方法来处理异常? try-catch块真的很丑
- 如何更好地检查两个 char 变量是否在一组值中?
- 有没有更好的方法对C++中的三个整数进行排序?
- 什么模板用法在阶乘中更好
- 平面缓冲区可以利用向量中的 0 吗?还是其他小波比哈尔变换更好?
- 我们应该如何使用枚举类进行索引(或者我们应该更好地避免这种情况)?
- Protobuf中重复字段的问题.使用重复字段进行序列化/反序列化的更好方法是什么?
- 比使用 s.str().c_str() 更好的表达?
- 使用指向成员的指针数组还是使用开关更好