自动演员表

时间:2008-09-23 15:55:09

标签: c++ casting

我目前正在患脑屁。我以前做过这个,但我不记得确切的语法,我不能看我写的代码,因为我当时在另一家公司工作。我有这个安排:

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。所有Reader都有加载代码 - 没有变量,编写器只有简单的编写代码。它需要是分开的,因为阅读和写作部分有各种管理器类和对话框,这些项目都不是项目文件的操作。

更新:我还需要能够调用P和PW的方法。因此,如果P有方法a()和PW作为方法调用b(),那么我可以:

PW p = c.GetP();
p.a();
p.b();

基本上是为了使转换变得透明。

10 个答案:

答案 0 :(得分:3)

你试图强制实际的变量,而不是指针。要做到这一点需要演员。但是,如果您的类定义如下所示:

class C

    {
        public: 
            P* GetP() const { return p; }
        private:
            P* p;
    }

然后,无论p是指向P,PW还是PR的指针,您的函数都不会改变,并且函数返回的P *上调用的任何(虚拟)函数都将使用P的实现, PW或PR取决于成员p是什么..

我想要记住的关键是Liskov Substitution Principle。由于PW和PR是P的子类,因此它们可以被视为Ps。但是,PW不能被视为PR,反之亦然。

答案 1 :(得分:3)

如果你想让这部分编译:



// ...
    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
};

答案 2 :(得分:2)

在上面的代码中,您与切片问题相反。

您要做的是从P分配到包含比源对象更多信息的PW或PR。你怎么做到这一点?假设P只有3个成员变量,但PW有12个额外成员 - 当你写PW p = c.GetP()时,这些成员的值会在哪里出现?

如果这个赋值实际 有效,这应该真正表明某种设计的怪异,那么我会实现PW::operator=(const P&)PR::operator=(const P&)PW::PW(const P&)和{ {1}}。但那天晚上我睡得不好。

答案 3 :(得分:1)

要通过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
// ...

答案 4 :(得分:1)

鉴于一切都是通过价值传递,这种做法是明智的。不确定这是不是你想的。

class P
{
public:
    template <typename T>
    operator T() const
    {
        T t;
        static_cast<T&>(t) = *this;
        return t;
    }
};

答案 5 :(得分:0)

也许你的意思是dynamic_cast运算符?

答案 6 :(得分:0)

它们不是完全可以互换的。 PW是P.P.PR是P.但P不一定是PW,也不一定是PR。您可以使用static_cast将指针从PW *转换为P *,或从PR *转换为P *。由于“切片”,您不应该使用static_cast将实际对象强制转换为超类。 E. g。如果你将PW的对象投射到P,PW中的额外东西将被“切掉”。您也无法使用static_cast从P *转换为PW *。如果你真的必须这样做,请使用dynamic_cast,它将在运行时检查对象是否实际上是正确的子类,如果不是,则给你一个运行时错误。

答案 7 :(得分:0)

我不确定你的意思,但请耐心等待。

他们已经有了。只要打电话给所有P,你可以假装PR和PW是P的。 PR和PW仍然不同。

使所有三个等效会导致Liskov principle出现问题。但是,如果它们真的相同,为什么你会给它们不同的名字呢?

答案 8 :(得分:0)

第二个和第三个是无效的,因为它是一个隐式的upcast - 这在C ++中是一个危险的东西。这是因为您要转换的类具有比它所分配的类更多的功能,因此除非您自己显式地转换它,否则C ++编译器将抛出错误(至少它应该)。当然,这稍微简化了一些事情(你可以使用RTTI来处理某些事情,这些事情可能与你想要安全地做什么有关,而不会引起坏对象的愤怒) - 但简单总是一种解决问题的好方法。 / p>

当然,正如其他一些解决方案所述,您可以解决这个问题 - 但我想在您尝试解决问题之前,您可能想重新考虑设计。

答案 9 :(得分:-1)

使用指向P而不是对象的引用或指针:

class C
{
 public:
  P* GetP() const { return p; }
 private:
  P* p;
};

这将允许PW *或PR *绑定到C.p.但是,如果需要从P转到PW或PR,则需要使用dynamic_cast&lt; PW *&gt;(p),它将返回p的PW *版本,或者p的NULL不是PW *(例如,因为它是PR *)。但是,Dynamic_cast有一些开销,如果可能的话,最好避免使用(使用虚拟)。

您还可以使用typeid()运算符来确定对象的运行时类型,但它有几个问题,包括您必须包含的问题以及它无法检测到额外的派生。