自动转换
我目前正在遭受脑部放屁。我之前已经做过,但是我不记得确切的语法,也看不到我编写的代码,因为当时我在另一家公司工作。我有这样的安排:
class P { // stuff }; class PW : public P { // more stuff }; class PR : public P { // more stuff }; class C { public: P GetP() const { return p; } private: P p; }; // ... P p = c.GetP( ); // valid PW p = c.GetP( ); // invalid PR p = c.GetP( ); // invalid // ...
现在,我想使P与PW和PR互换(因此PW和PR可以互换)。我可能可以摆脱强制转换,但是仅在此模块中,此代码更改已发生了好几次。我很确定这是一个操作员,但对于我的一生,我不记得发生了什么。
如何以最少的代码使P与PW和PR互换?
更新:进一步说明。 P代表Project,R和W分别代表Reader和Writer。读者拥有的只是不加载变量的代码,而作家拥有的仅仅是编写代码。它需要分开,因为"阅读"和"写作"部分具有各种管理器类和对话框,这与Projects真正关心的是项目文件的操作无关。
更新:我还需要能够调用P和PW的方法。因此,如果P具有方法a()和PW作为方法调用b(),那么我可以:
PW p = c.GetP(); p.a(); p.b();
基本上是使转换透明。
解决方案
在上面的代码中,我们遇到了与切片问题相反的问题。
我们要执行的操作是从P分配给PW或者PR,该PW或者PR包含比源对象更多的信息。你怎么做到这一点?假设P只有3个成员变量,但是PW还有12个其他成员,当我们编写PW p = c.GetP()
时,这些值在哪里?
如果此分配实际上是有效的,这实际上应该表明某种设计怪异,那么我将实现PW :: operator =(const P&)
和PR :: operator =(const P&)
,PW :: PW (const P&)
和PR :: PR(const P&)
。但是那天晚上我睡得不好。
使用对P的引用或者指针,而不是对象:
class C { public: P* GetP() const { return p; } private: P* p; };
这将允许PW *或者PR *绑定到C.p。但是,如果需要从P到PW或者PR,则需要使用dynamic_cast <PW *>(p),它将返回p的PW *版本,或者p的NULL不是PW *(对于例如,因为它是PR *)。不过,Dynamic_cast会有一些开销,如果可能,最好避免使用(使用虚拟)。
我们也可以使用typeid()运算符来确定对象的运行时类型,但是它有几个问题,包括必须包括的问题以及它不能检测到额外派生的问题。
也许我们是说dynamic_cast运算符?
它们不能完全互换。 PW是P。PR是P。但是P不一定是PW,也不一定是PR。我们可以使用static_cast将指针从PW *转换为P *,或者从PR *转换为P *。由于"切片",我们不应使用static_cast将实际对象转换为它们的超类。例如如果将PW的对象投射到P,则PW中的多余内容将被"切片"。我们也不能使用static_cast从P *强制转换为PW *。如果确实需要执行此操作,请使用dynamic_cast,它会在运行时检查该对象是否实际上属于正确的子类,如果不是,则会出现运行时错误。
我不确定意思是什么,但请忍受。
他们已经是。只需将所有内容都称为P,就可以假装PR和PW为P。
PR和PW仍然不同。
使所有三个相等将导致Liskov原理的麻烦。但是,如果它们真正等效,为什么还要给它们起不同的名字呢?
我们正在尝试强制实际变量,而不是指针。要做到这一点将需要演员。但是,如果类定义如下所示:
class C { public: P* GetP() const { return p; } private: P* p; }
然后,无论p是指向P,PW还是PR的指针,函数都不会改变,并且函数返回的P *上调用的任何(虚拟)函数都将使用P,PW或者PR中的实现取决于成员p是什么
我猜想要记住的关键是Liskov替代原则。由于PW和PR是P的子类,因此可以将它们视为P来对待。但是,不能将PW视为PR,反之亦然。
第二个和第三个将是无效的,因为它是隐式转换-这在C ++中是很危险的事情。这是因为要强制转换为的类比分配给其的类具有更多的功能,因此,除非我们自己明确强制转换,否则C ++编译器将抛出错误(至少应该如此)。当然,这只是稍微简化了事情(我们可以将RTTI用于可能与我们要安全执行的操作有关的某些事情,而不会引起不良对象的愤怒)-但是简单性始终是解决问题的好方法。
当然,如其他一些解决方案所述,我们可以解决此问题-但我认为在尝试解决该问题之前,我们可能需要重新考虑设计。
要通过P使PW和PR可用,我们需要使用引用(或者指针)。
因此,我们确实不需要更改C的接口,以便它返回引用。
旧代码中的主要问题是我们要将P复制到PW或者PR中。这是行不通的,因为PW和PR可能比P具有更多的信息,并且从类型的角度来看,类型P的对象不是PW或者PR。虽然PW和PR都是P。
将代码更改为此,它将编译:
如果要在运行时返回派生自P类的不同对象,则类C必须潜在地能够存储我们期望的所有不同类型并在运行时进行专门化。因此,在下面的类中,我允许我们通过传递指向将通过引用返回的对象的指针来进行专门化。为了确保对象是异常安全的,我将指针包装在一个智能指针中。
class C { public: C(std::auto_ptr<P> x): p(x) { if (p.get() == NULL) {throw BadInit;} } // Return a reference. P& GetP() const { return *p; } private: // I use auto_ptr just as an example // there are many different valid ways to do this. // Once the object is correctly initialized p is always valid. std::auto_ptr<P> p; }; // ... P& p = c.GetP( ); // valid PW& p = dynamic_cast<PW>(c.GetP( )); // valid Throws exception if not PW PR& p = dynamic_cast<PR>(c.GetP( )); // valid Thorws exception if not PR // ...
鉴于一切都是通过价值传递的,所以这种做法是明智的。不知道这是否是想法。
class P { public: template <typename T> operator T() const { T t; static_cast<T&>(t) = *this; return t; } };
如果要编译此部分,请执行以下操作:
// ... P p = c.GetP( ); // valid PW p = c.GetP( ); // invalid PR p = c.GetP( ); // invalid // ...
我们需要能够将P构造/转换为PW或者PR。
我们需要执行以下操作:
class PW : public P { PW(const P &); // more stuff }; class PR : public P { PR(const P &); // more stuff };
还是意思更像是:
class P { operator PW() const; operator PR() const; // stuff };