【问题标题】:C++ How to access derived class member from base class pointer without using a static_cast or dynamic_cast?C++ 如何在不使用 static_cast 或 dynamic_cast 的情况下从基类指针访问派生类成员?
【发布时间】:2017-06-22 08:45:47
【问题描述】:

看到下面question,我问自己是否有更好的方法来解决这个问题,所以不需要演员表。 考虑以下代码:

#include <iostream>

class Base
{
    public:
        virtual ~Base() {}
};

class Derived : public Base
{
    protected:
        int someVar = 2;

    public:
        int getSomeVar () {return this->someVar;}   
};


int main()
{
    Base    B = Base(); 
    Derived D = Derived();

    Base *PointerToDerived  = &D;
    Base *PointerToBase     = &B;

    std::cout << dynamic_cast<Derived*>(PointerToDerived)->getSomeVar() << "\n"; //this will work
    std::cout << dynamic_cast<Derived*>(PointerToBase)->getSomeVar() << "\n"; //this will create a runtime error

    return 0;

}

有没有更好的方法来设计这个,所以不需要强制转换并且可以避免这样的运行时错误?

【问题讨论】:

  • 它不会产生运行时错误,它会表现出未定义的行为,可能导致运行时错误
  • 这完全取决于你真正想要做什么......“通过基类访问派生类属性。”可能会出现在许多情况下,其中许多情况可以通过良好的设计直接避免......因此,如果没有更多关于您真正目标的信息,将很难提供好的建议......
  • 取决于你想做什么,最直接的解决方案可能只是虚函数
  • @nefas:我认为你是对的。访问者模式可能是处理这个问题的理想方式。你能举个例子吗?我无法绕过它。

标签: c++ pointers base dynamic-cast derived


【解决方案1】:

visitor pattern 使用双重分派,允许您使用特定于类的成员函数和成员变量(与通过层次结构可用的成员函数/变量相反)。

要实现访问者模式,您需要一个 visitorvisited 的层次结构(在您的示例中是 Base 和派生自 Base 的类)。 用你的例子,它会给出类似的东西:

class Base
{
    public:
        virtual ~Base() {}
        virtual void visit(Visitor) = 0;
};

class Derived : public Base
{
    protected:
        int someVar = 2;

    public:
        int getSomeVar () {return this->someVar;}
        void visit(Visitor& v) {
            v.visit(this);
         }
};
class Visitor {
    public:
         void visit(Derived& d) {
              bar(d.someVar);
         }
};

访问者背后的想法是Derived 中的this 知道它是真实类型,而多态变量(Base&amp;Base*)不知道。 OverridingBase::visit 允许您调用visit,他将向右侧发送Visitor::visit

如果你不覆盖Base::visit,当在Base&amp;(或Base*)上调用它时,它会在Base对象上调用函数(所以this的类型是Base* )。这就是为什么在示例中Base::visit 是抽象的,否则只会使错误更容易发生(例如忘记覆盖visit)。

Base 层次结构中添加新类型(例如Derived2 类)时,您需要添加两个函数:Derived2::visitVisitor::visit(Derived2)(或Visitor::visit(Derived2&amp;))。

编辑

live example

【讨论】:

  • 谢谢。在这种情况下,函数调用bar(d.someVar) 做了什么?
  • 你可以用你想要的替换它。这只是为了表明您可以使用不是类层次结构的成员函数或变量。
  • 好的,我想我明白了。我可以只使用返回类型int 的函数virtual int visit(Visitor) = 0;,然后将实现更改为visit(Derived&amp; d) {return d.getSomeVar;}。派生自Base 的所有其他类都有自己的visit 函数实现,而我在class visitor 中添加了重载。非常感谢。
  • 或者我当然可以做到,就像你指出的那样,随心所欲。
  • 我添加了一个活生生的例子。你可以让visit返回一个int,但你不能让Base::visit返回一个不同于Derived::visit的类型,因为它不会导致覆盖。
【解决方案2】:

是的,你可以在没有任何转换的情况下做到这一点:

class Base
{
    public:
        virtual ~Base() {}
        virtual int getSomeVar () = 0;
};

class Derived : public Base
{
    protected:
        int someVar = 2;

    public:
        virtual int getSomeVar () {return this->someVar;}   
};

int main()
{
//    Base    B = Base(); // will not compile, therefore you're safe
    Derived D = Derived();

    Base *PointerToDerived  = &D;
//    Base *PointerToBase     = &B;

    std::cout << PointerToDerived->getSomeVar() << "\n"; // no cast needed

    return 0;

}

如果您还希望能够构造 Base 类,您可以只提供该方法的“默认”实现,而不是让它纯虚拟。

(另外,在这种特殊情况下,方法应该是const

【讨论】:

  • 我正在起草相同的答案
  • 你必须小心这个解决方案,因为它很容易做一个糟糕的设计,比如添加一个带有string somevar2 成员的Derived2 类,并在Base 中添加一个getSomevar2,即使它Derived 拥有 getSomeVar2 是没有意义的
【解决方案3】:

如果我理解正确,您想在基类上调用派生类的函数。这没有很好的定义。

相反,您可以在基类中声明一个虚函数,该虚函数将被派生类覆盖。有关详细信息,请参阅 Why do we need Virtual Functions in C++?Wikipedia: Virtual Function

【讨论】:

    【解决方案4】:

    您可以在这里应用一个不错的“功能性”可重复使用技巧:

    template<typename Interface, typename Class, typename Function>
    void with(Class * anObject, Function f) {
       if (auto * i = dynamic_cast<Interface*>(anObject))
         f(*i);
    }
    

    用法:

    with<Derived>(PointerToBase, [](auto &derived) {
      std::cout << derived.getSomeVar() << "\n"; // wont be called
    }
    with<Derived>(PointerToDerived, [](auto &derived) {
      std::cout << derived.getSomeVar() << "\n"; // will be called
    }
    

    http://cpp.sh/9vzis 的示例

    【讨论】:

      【解决方案5】:

      避免运行时错误。

      Derived* derived = dynamic_cast<Derived*>(PointerToBase);
      if (derived != NULL) {
          std::cout << ->getSomeVar() << "\n"; 
      }
      

      当然,您不能将Base 转换为Derived

      【讨论】:

        猜你喜欢
        • 2011-01-27
        • 2016-07-31
        • 1970-01-01
        • 2015-02-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-10-01
        相关资源
        最近更新 更多