如何使适配器类适当地支持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
}
创建一个小部件: -
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);
易于使用。
缺点: AdapterConst
和Adapter
的代码将非常重复。
这是对以前解决方案的改进
让Adapter
(setPosition()
}派生自AdapterConst
(getPosition()
}。
缺点:这不简洁。我用2个班来完成单个任务!
这似乎是微不足道的,但在更大的代码库中,它根本不是很有趣。
具体而言,getPosition()
的位置将远离setPosition()
,例如在不同的文件中。
这会导致可维护性问题。
创建模板类。有很多方法,例如: -
Adapter<T =RigidBody OR const RigidBody >
Adapter<bool=true is const OR false is non-const >
缺点:在各方面,它都不够优雅。这太过分了。 (?)
我将遭受模板的缺点,例如标题中的一切。
我试图避免它。 It is evil.
class Adapter{
public: RigidBody* rigid;
void setUnderlying(const RigidBody* r){
rigid=const_cast< RigidBody*>(r);
}
....
};
我可以手动添加一些断言 它只是强调它是多么不专业: -
bool isConst;
void setUnderlying(const RigidBody* r){
...
isConst=true;
}
void setUnderlying(RigidBody* r){
...
isConst=false;
}
void setPosition(float a){
if(isConst){ /*throw some exception*/ }
....
}
test(
const
RigidBody* rigid)
更改为test(RigidBody* rigid)
。 RigidBody::setPosition()
更改为const
。 无论哪种方式,我的程序都不会const
- 再纠正,
但是单个Adapter
类就足够了。
在遇到const / non-const模式的任何地方,我真的必须做其中一件事吗? 请提供一个漂亮的解决方案(不需要完整的代码,但我不介意)
很抱歉这篇长篇文章。
编辑:在现实生活中,Adapter
是许多功能的参数
它像玩具一样传递。
大多数此类功能都不了解RigidBody
,因此不适合从调用someFunction(adapter)
的套件更改为someFunction(offset,rigidbody)
。
答案 0 :(得分:8)
你不应该坚持这个想法。这是C ++,而不是Java。
您的代码非常注重Java。我可以通过编写代码的方式看到它,使用指针并在需要时默默省略const
。
事实上,我个人看到的大多数糟糕的C ++代码都被写成“C inside classes”或“Java without GC”。它们都是编写C ++代码的极其糟糕的方法。
您的问题有一个惯用的解决方案:
抛弃大部分设计模式。它们对于默认情况下对象是引用类型的语言很有用。 C ++倾向于大多数时候将对象作为值类型而更喜欢静态多态(模板)而不是运行时多态(继承+覆盖)。
写两个类,一个是Adapter
,一个是ConstAdapter
。这就是标准库已经完成的工作。由于这个原因,每个容器都有不同的iterator
和const_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)
我的第一直觉是通过RigidBody
和Adaptor
到const 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