为什么在应调用派生类方法时调用基类方法

时间:2019-06-04 04:54:22

标签: c++

下面的代码在IMO应该调用eval版本时调用基类NotGate。它可以在R3调用中正常运行,而在R4调用中则无法正常工作。

在一次调用中添加了许多typeid来标识实际的“门”,并出于某种原因查找NotGate中门的类型在初始化期间已更改为存储为抽象门的变量当operator!被调用时。在我的完整代码中,operator*或其他类似的运算符不会发生。

#include <iostream>
using namespace std;
//---------------------------------------------------------------------------------------------------------------------
struct LGAbs {
    enum NodeState {
        Failure, Success, Running
    };
    LGAbs() {
    }
    virtual ~LGAbs() {
    }

    virtual NodeState eval();
};
//---------------------------------------------------------------------
LGAbs::NodeState LGAbs::eval() {
    return LGAbs::Failure;
}
//----------------------------------------------------------------------
struct TrueGate : public LGAbs {
    TrueGate() {
    }

    virtual LGAbs::NodeState eval() override {
        return Success;
    }
};
//----------------------------------------------------------------------
struct NotGate : public LGAbs {
    NotGate(LGAbs& g) :
        mG { g } {
    }
    virtual LGAbs::NodeState eval() override {
        cerr << typeid( mG).name() << endl;
        return mG.eval();
    }
    LGAbs& mG;
};
//----------------------------------------------------------------------
struct AndGate : public LGAbs {
    AndGate(LGAbs& g1, LGAbs& g2) :
        mG1 { g1 }, mG2 { g2 } {
    }
    virtual LGAbs::NodeState eval() override {
        return ((mG1.eval() == LGAbs::Success) && (mG2.eval() == LGAbs::Success)) ? LGAbs::Success : LGAbs::Failure;
    }
    LGAbs& mG1;
    LGAbs& mG2;
};
//----------------------------------------------------------------------
template <typename T1, typename T2>
inline AndGate operator *(T1&& lhs, T2&& rhs) {
    AndGate gate { lhs, rhs };
    // ***** Added typeid
    cerr << "op* " << typeid( gate).name() << '\t' << typeid( gate.mG1).name() << endl;
    return gate;
}
//---------------------------------------------------------------------
template <typename T1>
inline NotGate operator !(T1&& lhs) {
    NotGate gate(lhs);
    cerr << "op! " << typeid( gate).name() << '\t' << typeid( gate.mG).name() << endl;
    return gate;
}
//----------------------------------------------------------------------
int main() {
    TrueGate t;
    // *** Added r1
    auto r1 { (t * t) };
    cerr << "R1 " << r1.eval() << '\n' << endl;

    auto r2 { !t };
    cerr << "R2 " << r2.eval() << '\n' << endl;

    cerr << "R3 " << ( !(t * t)).eval() << '\n' << endl;

    auto r4 { !(t * t) };
    cerr << "R4 " << r4.eval() << '\n' << endl;

    cerr << "end " << endl;

    return 0;
}

输出显示R3如何正确求值,但是R4调用基类版本失败。

op* 7AndGate    8TrueGate
R1 1

op! 7NotGate    8TrueGate
R2 8TrueGate
1

R3 op! 7NotGate 7AndGate
7AndGate
1

op! 7NotGate    7AndGate
R4 5LGAbs
0

end 

1 个答案:

答案 0 :(得分:5)

有一个普遍的原则是您不遵守这里的原则...如果您有一个引用的成员变量,那么您可能做错了。情况并非总是如此。但是,如果您发现自己正在这样做,闹钟应该在您的头上响起。

eval上调用r4时,您正在调用未定义的行为。表达式(t * t)创建一个临时AndGate,然后在调用NotGate时将对它的引用塞入一个临时operator !中。然后将r4绑定到临时NotGate(从而延长其生命周期),然后销毁AndGate所引用的临时NotGate。此后,您在eval上调用NotGate,该生命通过分配给r4而得以延长。然后它在被破坏的eval上调用AndGate,这就是您得到未定义行为的时候了。

在这种特殊情况下,未定义的行为导致基类析构函数被调用。但是,未定义行为的本质是,在那里绝对可以发生任何事情,因此没有什么可以理解为实际发生的事情。

尽管如此,它以这种特殊方式发生的原因是AndGate的析构函数在调用基类析构函数之前恢复了基类的虚拟表指针,而后者没有执行任何操作。这根本不是保证的行为。正如我所说的,这是未定义的行为,因此绝对可以在这里进行任何操作。这就是使用特定的编译器在特定实例中发生的事情。

在R3情况下,您不调用未定义行为的原因是,保证临时对象可以持续到创建它们的表达式的末尾。因此,由于您不在某个地方存储对临时对象的引用,然后在以后的表达式中使用它,一切仍然有效。

有两种方法可以使程序避免未定义的行为。

首先是将类中LGAbs &的所有用法更改为::std::unique_ptr<LGAbs>,并在运算符中使用::std::make_unique<T>

第二个方法是将代表门的所有类都设置为模板类,并保留传入的项目的副本。然后应删除基类,并使eval不是虚函数。

任何一种都可以。

一个(模板方法)将仅允许您在编译时构造表达式,并将创建很多类(尽管其中大多数将优化为不存在)。它创建的代码将非常有效。但是当您不知道表达式的所有组成部分时,将无法在运行时构造表达式。

另一个会慢很多,并且会分配大量内存。但是,它甚至可以通过解析人们在程序中键入的表达式来使您在运行时构造表达式。

也有多种方法可以混合使用,但是事情开始变得复杂一些。

这里是如何使用unique_ptr方法执行此操作的示例。它需要C ++ 14才能工作:

#include <iostream>
#include <memory>
#include <type_traits>

using namespace std;
//---------------------------------------------------------------------------------------------------------------------
struct LGAbs {
    enum NodeState {
        Failure, Success, Running
    };
    LGAbs() {
    }
    virtual ~LGAbs() {
    }

    virtual NodeState eval();
};
//---------------------------------------------------------------------
LGAbs::NodeState LGAbs::eval() {
    return LGAbs::Failure;
}
//----------------------------------------------------------------------
struct TrueGate : public LGAbs {
    TrueGate() {
    }

    virtual LGAbs::NodeState eval() override {
        return Success;
    }
};
//----------------------------------------------------------------------
struct NotGate : public LGAbs {
    NotGate(::std::unique_ptr<LGAbs> g) :
         mG { ::std::move(g) } {
    }
    virtual LGAbs::NodeState eval() override {
        cerr << typeid( mG).name() << endl;
        return (mG->eval() == Success) ? Failure : Success;
    }
    ::std::unique_ptr<LGAbs> mG;
};
//----------------------------------------------------------------------
struct AndGate : public LGAbs {
    AndGate(::std::unique_ptr<LGAbs> g1, ::std::unique_ptr<LGAbs> g2) :
         mG1 { ::std::move(g1) }, mG2 { ::std::move(g2) } {
    }
    virtual LGAbs::NodeState eval() override {
        return (mG1->eval() == Success) && (mG2->eval() == Success) ?
            Success : Failure;
    }
    ::std::unique_ptr<LGAbs> mG1;
    ::std::unique_ptr<LGAbs> mG2;
};
//----------------------------------------------------------------------
template <typename T1, typename T2>
inline AndGate operator *(T1 &&lhs, T2 &&rhs) {
    using NRT1 = typename ::std::remove_reference<T1>::type;
    using NRT2 = typename ::std::remove_reference<T2>::type;
    AndGate gate { ::std::make_unique<NRT1>(::std::move(lhs)), ::std::make_unique<NRT2>(::std::move(rhs)) };
    return gate;
}
//---------------------------------------------------------------------
template <typename T1>
inline NotGate operator !(T1 &&lhs) {
    using NRT1 = typename ::std::remove_reference<T1>::type;
    NotGate gate{ ::std::make_unique<NRT1>(::std::move(lhs)) };
    cerr << "op! " << typeid( gate).name() << '\t' << typeid( *gate.mG).name() << endl;
    return gate;
}
//----------------------------------------------------------------------
int main() {
    TrueGate t;

    auto r2 { !t };
    cerr << "R2 " << r2.eval() << "\n\n";

    cerr << "R3 " << ( !(t * t)).eval() << "\n\n";

    auto r4 { !(t * t) };
    cerr << "R4 " << r4.eval() << "\n\n";

    cerr << "end \n";

    return 0;
}

这是一个使用模板的版本(可与C ++ 11一起使用)。请注意,两个版本都没有任何引用的成员变量:

#include <iostream>
#include <type_traits>

enum class NodeState {
   Failure, Success, Running
};

template <class T>
struct is_gate_class {
   static const bool value = ::std::is_same<decltype(reinterpret_cast<T const *>(0)->eval()), NodeState>::value;
};

template <class T>
class NotGate {
 public:
   explicit constexpr NotGate(T const &v) : v_(v) { }

   constexpr NodeState eval() const {
      return v_.eval() == NodeState::Success ?
         NodeState::Failure : NodeState::Success;
   }

 private:
   T v_;
};

template <class T1, class T2>
class AndGate {
 public:
   explicit constexpr AndGate(T1 const &v1, T2 const &v2) : v1_(v1), v2_(v2) {}

   constexpr NodeState eval() const {
      return (v1_.eval() != NodeState::Failure) &&
         (v2_.eval() != NodeState::Failure) ?
         NodeState::Success : NodeState::Failure;
   }

 private:
   T1 v1_;
   T2 v2_;
};

class TrueGate {
 public:
   constexpr NodeState eval() const {
      return NodeState::Success;
   }
};

template <class T>
constexpr typename ::std::enable_if<is_gate_class<T>::value, NotGate<T>>::type
operator !(T const &v)
{
   return NotGate<T>(v);
}

template <class T1, class T2>
constexpr typename ::std::enable_if<is_gate_class<T1>::value &&
                                    is_gate_class<T2>::value,
                                    AndGate<T1, T2>>::type
operator *(T1 const &v1, T2 const &v2)
{
   return AndGate<T1, T2>(v1, v2);
}

::std::ostream &operator <<(::std::ostream &os, NodeState const &ns)
{
   char const *valstr = nullptr;

   switch (ns) {
    case NodeState::Success:
      valstr = "Success";
      break;
    case NodeState::Failure:
      valstr = "Failure";
      break;
    case NodeState::Running:
      valstr = "Running";
      break;
   }
   return os << valstr;
}

int main()
{
   using ::std::cerr;

    TrueGate t;

    auto r1 { t };
    cerr << "R1 " << r1.eval() << "\n\n";

    auto r2 { !t };
    cerr << "R2 " << r2.eval() << "\n\n";

    cerr << "R3 " << ( !(t * t)).eval() << "\n\n";

    auto r4 { !(t * t) };
    cerr << "R4 " << r4.eval() << "\n\n";

    cerr << "end \n";

    return 0;
}