Pat*_*ght 10 c++ interface traits
我最近遇到了“特质”这个有趣且强大的概念,并尝试在 C++ 中理解/实现它们。据我了解,特征提供了一种方法,既可以扩展/调整现有代码的功能,又可以为类定义“接口”,而无需使用传统继承(以及随之而来的所有开销/问题)。我还看到这个概念似乎与C++中的CRTP设计模式密切相关。
举个例子,我用 C++ 编写接口的正常思维过程是定义一个具有纯虚方法的类。然后我可以创建它的子类并将指针传递给我的所有通用代码。然而我发现这有一些问题:
下面是一个简单、传统的 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();
}
Run Code Online (Sandbox Code Playgroud)
并且(基于此处的文章: 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();
}
Run Code Online (Sandbox Code Playgroud)
我对此有几个问题:
谢谢你!
这看起来像一个适配器,而不是 C++ 中使用的特征。
C++ 中的 Traits 就像std::numeric_limitsor std::iterator_traits。它接受一个类型并返回有关该类型的一些信息。默认实现处理一定数量的情况,您可以将其专门化来处理其他情况。
他编写的代码有一些问题。
在 Rust 中,这用于动态调度。模板版本不是动态的。
C++ 在值类型上蓬勃发展。对于嵌入引用,这不能是值类型。
检查很晚,在鸭子打字时,错误显示在特征代码中,而不是在调用站点上。
另一种方法是使用自由函数和概念以及 ADL。
turn_light_on(foo)并且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; }
};
}
Run Code Online (Sandbox Code Playgroud)
然后我们就有了OddLight:
namespace Odd {
class OddLight {
public:
void set(bool state);
};
}
Run Code Online (Sandbox Code Playgroud)
我们希望它是 a Light,所以我们这样做:
namespace Odd {
void on(::Light::light_tag, OddLight& odd){ odd.set(true); }
void off(::Light::light_tag, OddLight& odd){ odd.set(false); }
}
Run Code Online (Sandbox Code Playgroud)
然后
struct not_a_light{};
Run Code Online (Sandbox Code Playgroud)
如果我们有测试代码:
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
}
Run Code Online (Sandbox Code Playgroud)
请注意概念图:
namespace Odd {
void on(::Light::light_tag, OddLight& odd){ odd.set(true); }
void off(::Light::light_tag, OddLight& odd){ odd.set(false); }
}
Run Code Online (Sandbox Code Playgroud)
可以在 或namespace Odd中定义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))
{}
};
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以写:
void foo( Light::PolyLightRef light ) {
light.on();
light.off();
}
Run Code Online (Sandbox Code Playgroud)
我们得到动态调度;的定义foo可以对调用者隐藏。
扩展PolyLightRef到PolyLightValue并不是那么棘手——我们只需将分配(移动/复制)/构造(移动/复制)/销毁添加到vtable中,然后将状态填充到堆中或在void*某些情况下使用小缓冲区优化。
现在我们有一个完整的 Rust 式系统,基于动态“特征”的调度,特征在入口点进行测试(当您将它们作为Light auto或传递时PolyLightYYY),在特征命名空间或类型的命名空间中进行自定义,等等。
我个人期待c++23具有反射功能,以及自动化上述一些样板的可能性。
实际上有一个有用的变体网格:
RuntimePoly CompiletimePoly Concepts
PolyLightRef LightRef<T> Light&
PolyLightValue LightValue<T> Light
Run Code Online (Sandbox Code Playgroud)
你可以用类似 Rust 的方式来解决这个问题。
c++17推导指南可用于使CompiletimePoly使用起来不那么烦人:
LightRef ref = light;
Run Code Online (Sandbox Code Playgroud)
可以T为你推断
template<class T>
LightRef(T&)->LightRef<T>;
Run Code Online (Sandbox Code Playgroud)
(这可能是为您写的),并在呼叫站点
LightRefTemplateTakingFunction( LightRef{foo} )
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7599 次 |
| 最近记录: |