热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在这种情况下是否可以避免使用虚方法调用?

如何解决《在这种情况下是否可以避免使用虚方法调用?》经验,为你挑选了3个好方法。

我有一种数据类型必须存储在一个连续的数组中,为了更新这些数据,迭代的数组被迭代.棘手的部分是我希望有可能动态地改变任何对象的更新方式.

这是我到目前为止所提出的:

struct Update {
    virtual void operator()(Data & data) {}
};

struct Data {
    int a, b, c;
    Update * update;
};

struct SpecialBehavior : public Update {
    void operator()(Data & data) override { ... }
};

然后我会为每个数据对象分配一些类型的Update.然后在更新期间,所有数据都会传递给自己的更新仿函数:

for (Data & data : all)
   data->update(data);

据我所知,这就是战略模式.

我的问题:有没有办法更有效地做到这一点?某些方法可以实现相同的灵活性而无需调用虚拟方法的成本?



1> cmaster..:

虚函数调用的开销是多少?那么,实现必须做两件事:

    从对象加载vtable指针.

    从vtable加载函数指针.


这恰恰是两个记忆的间接.您可以通过将函数指针直接放在对象中来避免两者中的一个(避免从对象中查找vtable指针),这是ralismarks answer给出的方法.

这有一个缺点,它只适用于单个虚函数,如果你添加更多,你将使用函数指针膨胀你的对象,导致你的缓存压力更大,因此可能会降低性能.只要你只是替换一个虚函数,那就没关系,再添加三个,你的对象膨胀了24个字节.


除非确保编译器可以Update在编译时派生实际类型,否则无法避免第二个内存间接.而且由于似乎是使用虚函数在运行时执行决策的重点,所以你运气不好:任何"删除"间接的尝试都会产生更糟糕的性能.

(我说"删除"加上引号,因为你肯定能避免查找从内存中的函数指针,价格将是您要执行类似一个switch()else if()某些类型的标识值梯子从对象加载,这将变成是比仅仅从对象加载函数指针更昂贵.ralismarks的第二个解决方案明确地做了这个,而Vittorio Romeo的std::variant<>方法将它隐藏在模板中.间接并没有真正被删除,它只是隐藏在更慢的操作中.)std::variant<>


我的想法完全正确 tl; dr:只使用虚函数.
实际上,您错过了最大的开销:无法内联函数的机会成本.

2> ralismark..:

您可以使用函数指针代替.

struct Data;

using Update = void (*)(Data &);

void DefaultUpdate(Data & data) {};

struct Data {
    int a, b, c;
    Update update = DefaultUpdate;
};

void SpecialBehavior(Data & data) { ... };
// ...
Data a;
a.update = &SpecialBehaviour;

这避免了虚函数的成本,但仍然具有使用函数指针(较少)的成本.从C++ 11开始,您还可以使用非捕获lambdas(可以隐式转换为函数指针).

a.update = [](Data & data) { ... };

或者,您可以使用enum和switch语句.

enum class UpdateType {
    Default,
    Special
};

struct Data {
    int a, b, c;
    UpdateType behavior;
};

void Update(Data & data) {
    switch(data.behavior) {
        case UpdateType::Default:
            DoThis(data);
            break;
        case UpdateType::Special:
            DoThat(data);
            break;
    }
}


"这可以避免虚拟功能的成本,但仍然需要使用功能指针(更少)的成本" - 请引用您的来源,或显示证明它的基准.
这只会删除一个间接(从&#39;this`指针查找vtable指针),如果以这种方式实现多个`virtual`函数,则会使对象膨胀.与仅声明函数`virtual`相比,后一点可以轻松*降低*性能.
@cmaster交换机具有更多优化潜力,因为内联函数可以提取公共部分,从"数据"中提取常量折叠数据,...
不幸的是,`switch`甚至比函数指针慢.我想,只要只有一个功能可以切换,你的第一个解决方案实际上是最快的方法:-)
@cmaster:*开关甚至比功能指针慢*=>你测量过吗?当分支预测是正确的时,分支成本*实际上真的很小......直到看不见.

3> Vittorio Rom..:

如果你不需要开集多态 (即你事先知道所有可以派生的类型Update),你可以使用像或的变体:std::variantboost::variant

struct Update0 { void operator()(Data & data) { /* ... */ } };
struct Update1 { void operator()(Data & data) { /* ... */ } };
struct Update2 { void operator()(Data & data) { /* ... */ } };

struct Data {
    int a, b, c;
    std::variant update;
};

for (Data & data : all)
{
    std::visit(data.update, [&data](auto& x){ x(data); });
}

这将允许您:

避免virtual函数调用的成本.

Data以缓存友好的方式存储您的实例.

具有Update不同接口或任意不同状态的类.


另外,如果你想允许open-set多态但只允许通过operator()(Data&)接口,你可以使用类似的东西function_view,它基本上是对具有特定签名的函数对象的类型安全引用.

struct Data {
    int a, b, c;
    function_view update_function;
};

for (Data & data : all)
{
    data.update_function(data);
}


@cmaster OTOH,`std :: vector >`可能在缓存上更好,因为所有数据都存储在连续的内存中.在`std :: vector`中使用抽象类通常需要可能稀疏的堆分配(尽管在大多数情况下,您可能不会注意到差异)
推荐阅读
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了PHP常量的定义和使用方法,包括常量的命名规则、大小写敏感性、全局范围和标量数据的限制。同时还提到了应尽量避免定义resource常量,并给出了使用define()函数定义常量的示例。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
author-avatar
数到我答应我937
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有