在 C++ 中将指向基类的指针传递给派生类的成员函数

Posted

技术标签:

【中文标题】在 C++ 中将指向基类的指针传递给派生类的成员函数【英文标题】:Passing a pointer to a base class to derived class's member functions in C++ 【发布时间】:2009-12-14 22:28:57 【问题描述】:

如何从其实例的一个成员函数中访问派生类的数据,该成员函数接收指向基类的指针?我有:

    class Base
        public:
            virtual void compare(Base*)=0;
    ;

    class A: public Base
        int x;
        public:
            A();
            void compare(Base*);
    ;

    class B: public Base
        char c;
        public:
            void compare(Base*);
    ;

    A::A()
        x = 1;
    ;

    void A::compare(Base *p)
        cout<< "A's x is " << x << "\n";
        cout<< "Argument's x is " << p->x;
    ;

    void B::compare(Base* p)
        cout<< "B's compare() is running\n";
    ;

    int main(const int argc, const char *argv[])
        A a1 = A();
        A a2 = A();
        B b = B();
        a1.compare(&a2);
    

因为Base 没有数据成员x,所以这不起作用。在我写这篇文章的时候,我突然想到,通过指向 Base 接口类的指针来访问派生类的数据成员并不是一个好主意。

在我的问题中,我需要将不同的派生类与Base 类提供的相同接口进行比较。 Base 的接口必须包含compare(Base*) 成员函数。比较实例的方式可能因派生类而异。比较的逻辑显然应该在这些类的成员函数中编码。我应该如何实现这个模型?

【问题讨论】:

如果您可以比较“A”和“B”,结果会是什么。比如说,以某个复杂类作为其实例的“C”呢? 【参考方案1】:

除非您的派生类共享一些您想要比较的公共数据,您可以通过基类访问这些数据,否则没有简单的解决方案。 例如,在每个派生类中动态转换是多余的、缓慢的并且容易出错,除非您的派生类数量非常少。

较大类型层次结构的一种解决方案是double dispatch。

【讨论】:

双重分派在 C++ 中并不能很好地扩展,是吗?你能在不为每个新派生类型的基类添加虚函数原型的情况下实现它吗?如果不需要,可以贴代码吗? 不,它不能很好地扩展——但是 C++ 主要应该是一种静态类型的语言。我个人不知道有更好的通用版 DD。【参考方案2】:

您可以使用dynamic_cast 将基指针强制转换为派生类型之一。只要确保正确处理dynamic_cast无法转换指针的情况即可。

我应该补充一点,这是一种很臭的方法。如果对象确实具有可比性,那么它们应该在基类级别具有可比性。

【讨论】:

【参考方案3】:

dynamic_cast。

struct Base

    virtual bool compare(const Base*) = 0;
;

struct A : Base

    A()
    
        x = 1;
    
    virtual bool compare(const Base* other)
    
        const A* rhs(dynamic_cast<const A*>(other));
        if(other == NULL)  return false; 
        return x == rhs->x;
    
private:
    int x;
;

struct B : Base

    virtual bool compare(const Base* other)
    
        const B* rhs(dynamic_cast<const B*>(other));
        if(other == NULL)  return false; 
        return c == rhs->c;
    
private:
    char c;
;

【讨论】:

【参考方案4】:

之前我不得不努力克服这一点。在我的情况下,具体要求要求两个元素只有在它们实际上是相同的确切类型时才会比较相等。也就是说,在将苹果与苹果进行比较时,您只需要正向匹配,但在最衍生的级别(金苹果与金苹果,而不是皇家 gala 苹果)。

class base 
public:
   virtual bool compare( base const & rhs ) = 0;
;
class derived : public base 
public:
   virtual bool compare( base const & rhs ) 
      if ( typeid(*this) != typeid(rhs) ) return false;
      return *this == static_cast<derived const &>(rhs);
   
;
bool operator==( derived const & lhs, derived const & rhs );

请注意,我们最终使用typeid + static_cast 代替了更常见的动态转换习惯,以强制两种类型完全相同。如果我们使用了dynamic_cast,那么对称性可能会破坏并且:

class rederived : public derived 
public:
   virtual bool compare( base const & rhs ) 
      rederived const * p = dynamic_cast<rederived const *>(&rhs);
      return p && *this == *p;
   
;
void test() 
   derived d;
   rederived rd;
   d.compare( rd ); // compare if derived subobject is equal, possibly true
   rd.compare( d ); // always false, d cannot be dyncamically casted to rederived

这又是因为我们的特殊限制要求两个对象的类型完全相同。如果您只想检查它们在层次结构的一个级别中是否“兼容”(这本身就是一个不对称操作,因为左侧决定了您要在哪个层次结构级别执行比较)。

【讨论】:

【参考方案5】:

根据您的对象层次结构,您可能会跛行回家:

bool A::compare(Base *p)
    A *pa = dynamic_cast<A*>(p);
    if (!pa) return false;
    return x == pa->x;
;

实际上,实现多态比较存在严重的困难。不是最不重要的问题是,如果 a 是 A 的一个实例,而 c 是 A 的某个派生类 C 的一个实例,那么使用这种技术你可以很容易地以 a.compare(c) 为真,但是 c.compare (a) 是假的。

你可能会更好:

使您的比较成为非多态的,并将其放在调用者身上,以将它们作为 Base 类型进行比较,前提是他们只关心“就 Base 所知”而言它们是相等的。但这对调用者来说用处不大,而且在本例中 Base 没有数据成员时完全没有意义。 在 Base 中进行非虚拟比较,检查两个对象是否相等 typeid,如果不相等则返回 false,如果相等则调用两个对象的虚拟 compareImpl 函数。那么 A::comparisonImpl 就知道它的参数是 A 的一个实例,并且可以 static_cast 它:

.

class Base 
public:
    bool compare(Base *p) 
        if (typeid(*this) != typeid(*p)) return false;
        return compareImpl(p);
    
private:
    virtual bool compareImpl(Base *p) = 0;
;

class A : public Base 
    int x;
private:
    bool compareImpl(Base *p) 
        return x == static_cast<A*>(p)->x;
    
;

【讨论】:

避免不对称的一种方法是首先检查 typeid 是否相等。 一旦你检查了 typeid,你可以继续使用static_cast,因为演员是安全的,你不需要为类型信息“偿还”(dynamic_cast 更安全但更慢比static_cast 如果已知类型匹配,那么它们同样安全,后者会更快)。 是的。我的方式也利用了这一点。非虚拟函数避免了在每个派生类中重复 'orrible typeid 比较,但我认为它确实打开了Base 其他地方的一些错误代码可能直接调用compareImpl 的可能性,传入错误的参数。跨度> 【参考方案6】:

这个怎么样?

int main(const int argc, const char *argv[])
    Base a1 = new A();
    Base a2 = new A();
    Base b = new B();
    a1.compare(&a2); 

【讨论】:

以上是关于在 C++ 中将指向基类的指针传递给派生类的成员函数的主要内容,如果未能解决你的问题,请参考以下文章

C++继承,发送一个指向基类的指针

关于C++基类、派生类的引用和指针

为啥指向基类的派生类指针可以调用派生类成员函数? [复制]

基类与派生类的指针和成员函数调用原理

C++ 成员函数指针和 STL 算法

通过基类指针C++访问派生类的成员