了解 C++ 特性并使其高效

2023-11-26

我最近遇到了“特质”这个有趣且强大的概念,并尝试在 C++ 中理解/实现它们。据我了解,特征提供了一种方法,既可以扩展/调整现有代码的功能,又可以为类定义“接口”,而无需使用传统继承(以及随之而来的所有开销/问题)。我还看到这个概念似乎与C++中的CRTP设计模式密切相关。

举个例子,我用 C++ 编写接口的正常思维过程是定义一个具有纯虚方法的类。然后我可以创建它的子类并将指针传递给我的所有通用代码。然而我发现这有一些问题:

  1. 需要从多个接口继承的类需要使用多重继承,这可能会变得非常复杂并引入“钻石模式”问题。
  2. 形成严格的“是”关系,但这并不总是意图。例如,如果我描述光的接口,则模拟光并不是真正的光,它仅具有“特征”/像光一样起作用。我的意思是,通用 Light 接口并不真正具有实现需要继承的共性,它只是定义了实现应该如何表现。
  3. 虚拟方法和继承允许完全动态多态性,这会产生不必要的开销。在我的大多数代码中,我一次只会使用接口的单个​​实现,因此我不需要动态选择正确的实现,我只需要让接口的“用户”足够通用对于不同的实现。

下面是一个简单、传统的 Light 界面示例:

class Light {
public:
    virtual void on() = 0;
    virtual void off() = 0;
};

class MyLight : public Light {
public:
    void on() override;
    void off() override;
};

void lightController(Light& l) {
    l.on();
    l.off();
}

并且(基于此处的文章:https://chrisbranch.co.uk/2015/02/make-your-c-interfaces-trait-forward/)我认为这是同一概念的“基于特征”的实现:

template<typename T>
class Light {
public:
    Light(T& self) : _self(self) {}

    void on() { _self.on(); }
    void off() { _self.off(); }

private:
    T& _self;
};

class MyLight {
public:
    void on();
    void off();
};

class OddLight {
public:
    void set(bool state);
};

template<>
class Light<OddLight> {
public:
    Light(OddLight& self) : _self(self) {}
    
    void on() { _self.set(true); }
    void off() { _self.set(false); }
    
private:
    OddLight& _self;
};

template<typename T>
void lightUser1(T& l) {
    Light<T> light(l);

    light.on();
    light.off();
}

template<typename T>
void lightUser2(Light<T>& l) {
    light.on();
    light.off();
}

我对此有几个问题:

  1. 因为,要使用这样的特征,您(临时)创建一个新的 Light 实例,是否有与此相关的内存开销?
  2. 是否有更有效的方法来记录特定类“实现”给定特征?
  3. 本文提到了两种为界面定义“用户”的方法。我已经在上面展示了两者。 lightUser2 似乎是最完善的文档(它明确指出该函数需要 Light 特征的某种实现),但是它要求将实现显式转换为函数外部的 Light。是否有方法可以同时记录用户的意图和直接传递的所有实现?

谢谢你!


这看起来像一个适配器,而不是 C++ 中使用的特征。

C++ 中的特征就像std::numeric_limits or std::iterator_traits。它接受一个类型并返回有关该类型的一些信息。默认实现处理一定数量的情况,您可以将其专门化来处理其他情况。


他编写的代码有一些问题。

  1. 在 Rust 中,这用于动态调度。模板版本不是动态的。

  2. C++ 在值类型上蓬勃发展。对于嵌入引用,这不能是值类型。

  3. 检查很晚,在鸭子打字时,错误显示在特征代码中,而不是在调用站点上。

另一种方法是使用自由函数和概念以及 ADL。

turn_light_on(foo) and turn_light_off(foo)可以通过 ADL 进行默认设置和查找,从而允许自定义现有类型。如果您想避免“一个命名空间”问题,您可以包含一个接口标记。

namespace Light {
  struct light_tag{};
  template<class T>
  concept LightClass = requires(T& a) {
    { a.on() };
    { a.off() };
  };
  void on(light_tag, LightClass auto& light){ light.on(); }
  void off(light_tag, LightClass auto& light){ light.off(); }
  // also, a `bool` is a light, right?
  void on(light_tag, bool& light){ light=true; }
  void off(light_tag, bool& light){ light=false; }
  template<class T>
  concept Light = requires(T& a) {
    { on( light_tag{}, a ) };
    { off( light_tag{}, a ) };
  };
  void lightController(Light auto& l) {
    on(light_tag{}, l);
    off(light_tag{}, l);
  }
  struct SimpleLight {
    bool bright = false;
    void on() { bright = true; }
    void off() { bright = false; }
  };
}

那么我们就有了我们的OddLight:

namespace Odd {
  class OddLight {
  public:
    void set(bool state);
  };
}

我们希望它成为Light,所以我们这样做:

namespace Odd {
  void on(::Light::light_tag, OddLight& odd){ odd.set(true); }
  void off(::Light::light_tag, OddLight& odd){ odd.set(false); }
}

then

struct not_a_light{};

如果我们有测试代码:

int main() {
  Light::SimpleLight simple;
  Odd::OddLight odd;
  not_a_light notLight;
  Light::lightController(simple);
  Light::lightController(odd);
  // Light::lightController(notLight); // fails to compile, error is here
}

请注意概念图:

namespace Odd {
  void on(::Light::light_tag, OddLight& odd){ odd.set(true); }
  void off(::Light::light_tag, OddLight& odd){ odd.set(false); }
}

可以定义为namespace Odd or namespace Light.

如果您想将其扩展到动态调度,则必须手动编写类型擦除。

namespace Light {
  struct PolyLightVtable {
    void (*on)(void*) = nullptr;
    void (*off)(void*) = nullptr;
    template<Light T>
    static constexpr PolyLightVtable make() {
      using Light::on;
      using Light::off;
      return {
        [](void* p){ on( light_tag{}, *static_cast<T*>(p) ); },
        [](void* p){ off( light_tag{}, *static_cast<T*>(p) ); }
      };
    }
    template<Light T>
    static PolyLightVtable const* get() {
      static constexpr auto retval = make<T>();
      return &retval;
    }
  };
  struct PolyLightRef {
    PolyLightVtable const* vtable = 0;
    void* state = 0;

    void on() {
        vtable->on(state);
    }
    void off() {
        vtable->off(state);
    }
    template<Light T> requires (!std::is_same_v<std::decay_t<T>, PolyLightRef>)
    PolyLightRef( T& l ):
        vtable( PolyLightVtable::get<std::decay_t<T>>() ),
        state(std::addressof(l))
    {}
  };
}

现在我们可以写:

void foo( Light::PolyLightRef light ) {
    light.on();
    light.off();
}

我们得到动态调度;的定义foo可以对呼叫者隐藏。

延伸PolyLightRef to PolyLightValue是不是很棘手——我们只需将分配(移动/复制)/构造(移动/复制)/销毁添加到虚函数表中,然后将状态填充到堆中void*或者在某些情况下使用小缓冲区优化。

现在我们有一个完整的 Rust 式系统,基于动态“特征”的调度,这些特征在入口点进行测试(当您将它们传递为Light auto or PolyLightYYY),在特征命名空间中进行定制or在类型的命名空间等中。

我个人很期待c++23具有反射,以及自动化上述一些样板的可能性。


实际上有一个有用的变体网格:

RuntimePoly        CompiletimePoly     Concepts
PolyLightRef       LightRef<T>         Light&
PolyLightValue     LightValue<T>       Light

你可以用类似 Rust 的方式来解决这个问题。

c++17演绎指南可用于制作CompiletimePoly使用起来不那么烦人:

LightRef ref = light;

可以推论T为你与

template<class T>
LightRef(T&)->LightRef<T>;

(这可能是为您写的),并在呼叫站点

LightRefTemplateTakingFunction( LightRef{foo} )

带有错误消息的实时示例

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

了解 C++ 特性并使其高效 的相关文章

  • C# 打印问题(RichTextBox)

    我想打印我的 RichTextBox eintragRichTextBox 的内容 我现在有这个代码 private void druckenPictureBox Click object sender EventArgs e PrintD
  • 使用 CLion 进行 OpenCV Windows 设置

    我想在 Windows 上为 CLion IDE 设置 OpenCV 我尝试使用 OpenCV 3 1 和 2 4 得到相同的结果 我有 Windows 10 64 位 CLion 使用 cygwin 环境 到目前为止我做了什么 1 从Op
  • 将 new 与 decltype 一起使用

    T t T is an implementation detail t new T want to avoid naming T to allow for flexibility t new decltype t error cannot
  • Poco c++Net:Http 从响应中获取标头

    我使用 POCO C Net 库进行 http 我想尝试制定持久缓存策略 首先 我认为我需要从缓存标头中获取过期时间 并与缓存值进行交叉检查 如果我错了 请告诉我 那么我如何从中提取缓存头httpResponse 我已经看到你可以用 Jav
  • 在 Java 中创建 T 的新实例

    在C 中 我们可以定义一个泛型class A
  • C++ 长 switch 语句还是用地图查找?

    在我的 C 应用程序中 我有一些值充当代表其他值的代码 为了翻译代码 我一直在争论使用 switch 语句还是 stl 映射 开关看起来像这样 int code int value switch code case 1 value 10 b
  • 有没有办法在 xcode 上使用 c++0x ?我想使用 gcc 4.4 或更高版本

    我想使用 gcc 4 4 或更高版本进行 iphone 开发 有人知道怎么做吗 不 你不知道 相信我 你不会 Apple 仍保留 gcc 4 2 1 因为 4 2 2 及更高版本使用 GPLv3 这意味着他们必须放弃对其平台的控制 对于 i
  • 在 C# 中调用 C++ 库 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有很多用 C 编写的库 我想从 C 调用这些库 但是 我遇到了很多问题 我想知道是否有书籍或指南告诉我如何做到这一点 Dll导入 htt
  • 从模板切换传递的类型

    在 C 中是否可以检查传递给模板函数的类型 例如 template
  • C# 开源 NMEA 解析器 [已关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找 C 开源 NMEA 解析器 嗯 我自己也不熟悉 但是一些快速搜索显示了一个代码项目 htt
  • 将表(行)与 OpenXML SDK 2.5 保持在一起

    我想在 Word 文档中生成多个表 每行 2 行 但我想将这两行保留在一起 如果可能的话 new KeepNext 第一行不起作用 new KeepNext 第一行的最后一段不起作用 new CantSplit 放在桌子上不起作用 在所有情
  • 析构函数中的异步操作

    尝试在类析构函数中运行异步操作失败 这是代码 public class Executor public static void Main var c1 new Class1 c1 DoSomething public class Class
  • 在 asp.net MVC 中使用活动目录进行身份验证

    我想使用活动目录对我的 asp net mvc 项目中的用户进行身份验证 在网上冲浪了几个小时后 我没有找到任何对我有用的东西 我已经看到了所有结果 但什么也没有 我尝试按照许多帖子的建议编辑我的 web config 如果有人可以帮助我提
  • 引用/指针失效到底是什么?

    我找不到任何定义指针 引用无效在标准中 我问这个问题是因为我刚刚发现 C 11 禁止字符串的写时复制 COW 据我了解 如果应用了 COW 那么p仍然是一个有效的指针并且r以下命令后的有效参考 std string s abc std st
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • ASP.NET MVC 路由:如何从 URL 中省略“索引”

    我有一个名为 StuffController 的控制器 具有无参数索引操作 我希望从表单中的 URL 调用此操作mysite com stuff 我的控制器定义为 public class StuffController BaseContr
  • 逆向工程 ASP.NET Web 应用程序

    我有一个 ASP NET Web 应用程序 我没有源代码 该 bin 包含 10 个程序集和一个 compiled 文件 我在 App Code dll 上使用 Reflector 它向我显示了类和命名空间之类的东西 但它太混乱了 有没有什
  • .NET 4 的条件编译[重复]

    这个问题在这里已经有答案了 可能的重复 条件编译和框架目标 https stackoverflow com questions 2923210 c sharp conditional compilation and framework ta
  • CUDA 8 编译错误 -std=gnu++11

    我正在尝试转换一些代码以使用 CUDA 并且我认为我遇到了兼容性问题 我们使用CMake 这些是我使用的 gcc 和 CUDA 版本 gcc version gcc Ubuntu 5 4 0 6ubuntu1 16 04 5 5 4 0 2
  • 为什么匹配模板类上的部分类模板特化与没有模板匹配的另一个部分特化不明确?

    这个问题可能很难用标题中的句子来描述 但这里有一个最小的例子 include

随机推荐

  • R-更改数据框中列的编码?

    我正在尝试更改数据框中列的编码 stri enc mark data updated text 1 UTF 8 ASCII ASCII UTF 8 ASCII ASCII UTF 8 UTF 8 UTF 8 10 ASCII ASCII U
  • not() 和ends-with() 的Xpath 错误

    我有以下 Xpath 表达式 not input ends with Copyright 我希望它能够为我提供所有元素 输入除外 以及以 版权 结尾的任何属性值 我在 Selenium 2 Java API 中执行它webDriver fi
  • C++ 中 min 和 max 函数的使用

    从 C 来看 有std min and std max优于fmin and fmax 为了比较两个整数 它们提供基本相同的功能吗 您是否倾向于使用这些函数集中的一组 还是更喜欢编写自己的函数 也许是为了提高效率 可移植性 灵活性等 Note
  • ClassNotFoundException:net.sourceforge.jtds.jdbc.Driver

    我有连接到 MS SQL 数据库并获取一些数据的 java 代码 在运行代码之前 我在 Unix 服务器中设置了类路径 它以前工作得很好 但由于某种原因 几天前运行的同一个 jar 文件抛出了 Class not found 异常java
  • 不带 typedef 关键字的结构

    我目前正在学习有关structC 中的数据结构以及如何在该结构前加上typedef关键词 这会导致实际结构的变量名称被放置在不同的命名空间中 如几个不同参考文献中所述 C 中 struct 和 typedef struct 的区别 type
  • 如何使用 Cocoa 标记文件和文件夹

    我想用某种颜色 图像 标记文件和文件夹 如何才能实现这一目标 我尝试使用图标服务 它适用于文件 但不适用于文件夹 我看到这种行为有效Dropbox 10 4 10 5 和 10 6 如何做到这一点 博客文章Cocoa 教程 自定义文件夹图标
  • 平面文件与数据库 - 速度?

    我正在制作一个聊天程序 我需要一个地方来存储消息 客户端将每隔 x 秒与服务器联系一次最后收到的消息 id 服务器将在客户端加入的房间中查找 id 高于该 id 的所有消息 由于我不会永远存储内容 因此我正在考虑使用仅包含最后 40 条左右
  • 64 位架构中的汇编寄存器

    继回答有关汇编寄存器大小的问题 首先 尺寸是多少eax ax ah以及 64 位架构中的对应产品 如何访问单个寄存器的字节以及如何访问所有64位寄存器的八个字节 我希望双方都受到关注x86 64 x64 and Itanium处理器 其次
  • 如何在 Chrome 中将 localStorage 数据写入文本文件

    我想将 localStorage 项写入文本文件 并希望调用用户将文件存储在指定位置 请帮助我扩展代码 var data JSON parse localStorage getItem pid var Text2Write for var
  • 以编程方式分析java堆转储文件

    我想编写一个程序 最好用java 来解析和分析java堆转储文件 由jmap创建 我知道有很多很棒的工具已经可以这样做 jhat eclipse 的 MAT 等 但我想从我的应用程序的特定角度来分析堆 在哪里可以阅读有关堆转储文件的结构 如
  • 什么时候需要使用标志 -stdlib=libstdc++?

    什么时候需要使用use flag stdlib libstdc 使用 gcc 编译时的编译器和链接器 编译器会自动使用libstdc 吗 我在 Ubuntu 13 10 上使用 gcc4 8 2 我想使用 c 11 标准 我已经通过了 st
  • 不稳定的 javax.jms.JMSException:同行已处置

    我在使用 Java JMS 时遇到了不稳定的问题 暂时工作正常 但会不规律地抛出以下异常并中断执行 值得注意的是 这种情况是在没有停止代理的情况下发生的 javax jms JMSException Peer vm test 1 dispo
  • 无法在写入上下文中使用方法返回值

    我认为下面的代码应该可以工作 但事实并非如此 已编辑 现在适用于 PHP 5 5 if empty r gt getError Where getError 很简单 public function getError return this
  • 使用备用凭据通过 VBscript 进行安全 LDAP 对象操作

    我知道使用具有显式凭据的 ADsDSOobject 来连接到 AD 对象以读取属性 列出成员等 以及用于操作这些对象 添加组成员 更改属性 的 GetObject LDAP 方法等 但是有没有办法通过显式凭证来操作属性和成员资格 我指的第一
  • 如何从名称中获取选择器?

    我有一个 NSString 其中包含我想用 PerformSelector 调用的选择器的名称 如何从字符串中获取对选择器的引用 NSSelectorFromString name
  • FOSElasticaBundle 和 Doctrine Hydration

    我正忙着检查 elasticsearch 来寻找我正在开始的新项目 我目前正在运行 Symfony2 5 以及最新的 FOSElasticaBundle 等等 我正在对捆绑包的性能进行一些基准测试 我知道弹性本身非常快 但我遇到了一个小问题
  • 如何将 Ember 数据与嵌套资源结合使用

    我的应用程序后端有多种资源 为每个资源公开一个模型 所有其他资源的入口点是通过User模型 我的意思是 给定User我们可以找BlogPost 给定一个BlogPost我们可以找Comments etc 用 Ember 术语来说 我们可以说
  • 织物的独立 fabfile?

    是否可以使 fabfile 成为独立的 我不太喜欢运行外部工具 fab 如果我设法获得独立的 fabfile 我可以从 Eclipse Pydev IDE 中运行该文件 轻松调试它 使用项目配置和路径等 为什么这不起作用 from fabr
  • 序列化一个可为空的 int

    我有一个可以为 null int 的类 设置为序列化为 xml 元素的数据类型 有什么方法可以设置它 以便 xml 序列化程序在值为 null 时不会序列化该元素吗 我尝试添加 System Xml Serialization XmlEle
  • 了解 C++ 特性并使其高效

    我最近遇到了 特质 这个有趣且强大的概念 并尝试在 C 中理解 实现它们 据我了解 特征提供了一种方法 既可以扩展 调整现有代码的功能 又可以为类定义 接口 而无需使用传统继承 以及随之而来的所有开销 问题 我还看到这个概念似乎与C 中的C