适配器模式:支持基本数据,可以是const或非const,优雅

时间:2017-04-27 10:00:07

标签: c++ function const adapter c++14

如何使适配器类适当地支持const和非const底层数据?

具体示例

RigidBody是描述对象物理属性的类 这是它的简化版本(1D): -

class RigidBody{
    float position=1;
    public: float getPosition()const{ return position;}
    public: void setPosition(float ppos){ position=ppos;}
};

Adapter封装了RigidBody 它为get/set position提供了一个小失真的功能: -

class Adapter{
    public: RigidBody* rigid; int offset=2;
    public: float getPosition(){
        return rigid->getPosition()+offset;     //distort
    }
    public: void setPosition(float ppos){
        return rigid->setPosition(ppos-offset); //distort
    }
};

我可以使用RigidBody间接设置Adapter的位置: -

int main() {
    RigidBody rigid;
    Adapter adapter;  //Edit: In real life, this type is a parameter of many function
    adapter.rigid=&rigid;
    adapter.setPosition(5);
    std::cout<<adapter.getPosition();//print 5
    return 0;
}

一切正常(demo)。

目的

我想创建一个接收 const RigidBody* rigid的新功能。
我应该能够通过使用适配器从中读取(例如getPosition())。

但是,我真的不知道如何优雅地做到这一点。

void test(const RigidBody* rigid){
    Adapter adapter2; 
    //adapter2.rigid=rigid; //not work, how to make it work?
    //adapter2.setPosition(5); //should not work
    //adapter2.getPosition();  //should work 
}

我糟糕的解决方案

解决方案A1(2个适配器+ 1个小部件)

创建一个小部件: -

class AdapterWidget{
    public: static Adapter createAdapter(RigidBody* a);
    public: static AdapterConst createAdapter(const RigidBody* a);
};

AdapterConst只能getPosition(),而AdapterConst可以获取和设置。

我可以像以下一样使用它: -

void test(const RigidBody* rigid){
    auto adapter=AdapterWidget::createAdapter(rigid);

易于使用。

缺点: AdapterConstAdapter的代码将非常重复。

解决方案A2(+继承)

这是对以前解决方案的改进 让AdaptersetPosition()}派生自AdapterConstgetPosition()}。

缺点:这不简洁。我用2个班来完成单个任务!
这似乎是微不足道的,但在更大的代码库中,它根本不是很有趣。

具体而言,getPosition()的位置将远离setPosition(),例如在不同的文件中。
这会导致可维护性问题。

解决方案B(模板)

创建模板类。有很多方法,例如: -

  • Adapter<T =RigidBody OR const RigidBody >
  • Adapter<bool=true is const OR false is non-const >

缺点:在各方面,它都不够优雅。这太过分了。 (?)
我将遭受模板的缺点,例如标题中的一切。

解决方案C1(const_cast)

我试图避免它。 It is evil.

class Adapter{
    public: RigidBody* rigid; 
    void setUnderlying(const RigidBody* r){
        rigid=const_cast< RigidBody*>(r);
    }
    ....
};

解决方案C2(+手动断言)

我可以手动添加一些断言 它只是强调它是多么不专业: -

    bool isConst;
    void setUnderlying(const RigidBody* r){
        ...
        isConst=true;
    }
    void setUnderlying(RigidBody* r){
        ...
        isConst=false;
    }
    void setPosition(float a){
        if(isConst){ /*throw some exception*/ }
        ....
    }

解决方案D(逃跑)

  • 懒惰:test( const RigidBody* rigid)更改为test(RigidBody* rigid)
  • 疯狂:RigidBody::setPosition()更改为const

无论哪种方式,我的程序都不会const - 再纠正,
但是单个Adapter类就足够了。

问题

在遇到const / non-const模式的任何地方,我真的必须做其中一件事吗? 请提供一个漂亮的解决方案(不需要完整的代码,但我不介意)

很抱歉这篇长篇文章。

编辑:在现实生活中,Adapter是许多功能的参数 它像玩具一样传递。

大多数此类功能都不了解RigidBody,因此不适合从调用someFunction(adapter)的套件更改为someFunction(offset,rigidbody)

4 个答案:

答案 0 :(得分:8)

你不应该坚持这个想法。这是C ++,而不是Java。

您的代码非常注重Java。我可以通过编写代码的方式看到它,使用指针并在需要时默默省略const

事实上,我个人看到的大多数糟糕的C ++代码都被写成“C inside classes”或“Java without GC”。它们都是编写C ++代码的极其糟糕的方法。

您的问题有一个惯用的解决方案:

  1. 抛弃大部分设计模式。它们对于默认情况下对象是引用类型的语言很有用。 C ++倾向于大多数时候将对象作为值类型而更喜欢静态多态(模板)而不是运行时多态(继承+覆盖)。

  2. 写两个类,一个是Adapter,一个是ConstAdapter。这就是标准库已经完成的工作。由于这个原因,每个容器都有不同的iteratorconst_iterator实现。你可以通过指针存储一些东西,或者通过const指针存储。试图将两者混合是容易出错的。如果有一个很好的解决方案,我们就不会为每个容器提供两个迭代器typedef。

答案 1 :(得分:2)

严格控制的const_cast对我来说似乎是一个很好的解决方案:

// Only provides read-only access to the object
struct ConstAdapter {
    int offset = 2;

    // Constructible from const and non-const RigidBodies
    ConstAdapter(RigidBody const *rigid)
    : _rigid{rigid} { }

    // Read-only interface
    float getPosition() {
        return rigid()->getPosition() + offset;     //distort
    }

    // Hidden away for consistency with Adapter's API
    // and to prevent swapping out an "actually non-const" RigidBody
    // for a "truly const" one (see  Adapter::rigid()`).
    RigidBody const *rigid() const { return _rigid; }

private:
    RigidBody const *_rigid;
};

// Inherits read-only functions, and provides write access as well
struct Adapter : ConstAdapter {

    // Only constructible from a non-const RigidBody!
    Adapter(RigidBody *rigid) : ConstAdapter{rigid} { }

    // Write interface
    void setPosition(float ppos){
        return rigid()->setPosition(ppos-offset); //distort
    }

    // Here's the magic part: we know we can cast `const` away
    // from our base class' pointer, since we provided it ourselves
    // and we know it's not actually `const`.
    RigidBody *rigid() const {
        return const_cast<RigidBody *>(ConstAdapter::rigid());
    }
};

关于:

  

具体而言,getPosition()的位置将远离setPosition(),例如在不同的文件中。   这会导致可维护性问题。

这不是问题。与Java不同,C ++允许在单个文件中使用多个类,并且实际上鼓励您将这些紧密相关的类组合在一起。函数的声明只有少数几行,它们的定义可以很好地组合在相应的.cpp文件中。

答案 2 :(得分:1)

使用引用而不是指针,让const-ness传播到Adapter。然后你可以安全地const_cast,如

template <class Rigid>
class Adapter{
    Rigid & rigid;
    int offset;
public: 
    Adapter(Rigid & rigid, int offset = 2) : rigid(rigid), offset(offset) {}
    float getPosition() const { return rigid.getPosition() + offset; }
    void setPosition(float ppos) { rigid.setPosition(ppos - offset); }
};

答案 3 :(得分:1)

我的第一直觉是通过RigidBodyAdaptorconst RigidBody访问const Adaptor。为了实现这一点,我们使用工厂来创建正确类型的Adaptor,并且在实现中,我们通过访问器方法访问底层RigidBody,该方法仅在允许的情况下(安全地)强制转换。

代码:

从私人成员开始:

class Adaptor
{
    RigidBody const& body;
    int offset;

    Adaptor(RigidBody body, int offset=2)
        : body(body),
          offset(offset)
    {}

    Adaptor(const Adaptor&) = delete;

构造函数是私有的,因此我们只能通过工厂创建实例。我们删除了复制构造函数,因此我们无法从const构造函数创建非const Adaptor

接下来,我们有了第一个重载对 - 实现中使用的访问器。我已将其标记为protected,以防您需要一系列类似的适配器。如果你愿意,它可以安全地public

protected:
    RigidBody& get_body() { return const_cast<RigidBody&>(body); }
    RigidBody const& get_body() const { return body; }

我们知道const_cast是安全的,因为我们只从非const Adaptor获得非const RigidBody,正如我们在另一个重载对中看到的那样,工厂:

public:
    static Adaptor *adapt(RigidBody& body, int offset = 2) { return new Adaptor{ body, offset }; }
    static Adaptor const *adapt(RigidBody const& body, int offset = 2) { return new Adaptor{ body, offset }; }

从这里开始,每个方法都被声明为const,除非它需要修改正文,就像你期望的那样。所以,如果你有一个const Adaptor(如果你是从const RigidBody构建它的话,那么就不能调用任何修改body的方法。并且实现不能修改{来自任何body方法中的{1}}。

const

您可以尝试在const方法中修改 float getPosition() const { // this uses the const get_body() return get_body().getPosition() + offset; } void setPosition(float ppos) { // this uses the mutable get_body() get_body().setPosition(ppos-offset); } 来证明安全性:

body

演示:

使用 void illegal() const { get_body().setPosition(0); // error: passing ‘const RigidBody’ as ‘this’ argument discards qualifiers body.setPosition(0); // error: passing ‘const RigidBody’ as ‘this’ argument discards qualifiers } }; ,适配器允许所有操作;使用const RigidBody,适配器仅允许RigidBody操作:

const