>假设我有一个虚拟类:
class Tester {
virtual void Validate();
}
class Test1 : Tester {
void Validate(...) {
/implementation
}
}
class Test2 : Test1 {
void Validate(...) {
/implementation
}
}
据我了解,虚拟应该使其动态绑定。但我不确定如果我们有分层类会发生什么(即。测试2继承测试1继承测试器):
void RunTest(Tester test) {
test.Validate(...);
}
...
Test2 test_candidate = Test2();
RunTest(test_candidate)
这将调用哪种Validate
方法?
void RunTest(Tester test)
函数的此参数是类型为Tester
的对象。它不是Test1
也不是Test2
,因此调用不能是除Tester::Validate
以外的任何东西。
当您将Test2
作为参数传递时,它将通过复制基子对象转换为Tester
。
虚拟调度需要间接寻址。必须通过引用或指针调用函数,因为只有引用或指针才能指向派生对象的基子对象。
回答您的问题
(请参阅下面或此处,为什么您的示例代码的行为不是这样的)
"迟到"绑定的行为
后期绑定始终调用实例类型最专用的虚拟函数。在你的例子中,所有的类,Tester
、Test1
和Test2
都会调用它们自己的Validate()
,因为Test
和Test2
在每个子类中覆盖这个函数。
但是,如果你有
Test3 : public Test1 { /* Not overriding Validate() */ }
在Test3
的实例上调用Validate()
会调用Test1::Validate()
,因为这是最专业的虚拟实现(它覆盖了Tester::Validate()
)。
示例代码中的问题
在示例代码中,函数RunTest(Tester)
按值传递参数。如果您传递类型Test1
的参数,它会创建类型Tester
的临时副本,如其他答案所示,并且您的编译器根本不允许它而没有警告。这个临时副本在RunTest(Tester)
中使用,并且将始终调用Tester::Validate()
,这可能不是您作为参数传递给RunTest(Tester)
的对象的实现。
若要使用后期绑定,应传递引用或指针:
RunTest(Tester *t) { t->Validate(); }
或
RunTest(Tester &t) { t->Validate(); }
使用其中之一将确保行为如上所述(您的答案)。
您提供的代码存在很多问题:
1. 私有继承 + 私有函数定义
您需要将方法声明为公共方法,以便能够从外部函数(如RunTest
)调用它们。在这种情况下,您可能还希望公开继承,因此您可以将Test2
类转换回Tester
。
例如:
class Tester {
public:
virtual void Validate();
};
class Test1 : public Tester {
public:
void Validate();
};
class Test2 : public Test1 {
public:
void Validate();
};
2. 一些建议
如果要重写子类中的虚拟方法,可以使用override
将函数标记为这样。这样,如果函数无法被覆盖(例如,由于参数不匹配),您将收到编译时错误 (我假设你的意思是void Validate() {}
而不是void Validate(...)
)
此外,如果您的类包含任何虚拟方法,则最好同时提供虚拟析构函数。(正确清理所有成员,以防它被指向其基类的指针删除)
例如:
class Tester {
public:
virtual void Validate() {
// TODO
}
virtual ~Tester() = default;
};
class Test1 : public Tester {
public:
void Validate() override {
// TODO
}
};
class Test2 : public Test1 {
public:
void Validate() override {
// TODO
}
};
3.RunTest()
函数将对 Test2 对象进行切片
您将一个Tester
实例传递给 value.
的RunTest()
,这将导致对象被切片,即您将丢失派生对象中存储的所有内容。
void RunTest(Tester test);
// this
Test2 t2;
RunTest(t2);
// is equivalent to:
Test2 t2;
Test t = t2;
RunTest(t);
所以本质上你只用一个Test
对象调用RunTest()
方法,而不是Test2
.
您可以通过引用或指针来解决此问题:
void RunTest(Tester& test) {}
// or
void RunTest(Tester* test) {}
<小时 />工作示例
#include <iostream>
class Tester {
public:
virtual void Validate() {
std::cout << "Tester Validate" << std::endl;
}
virtual ~Tester() = default;
};
class Test1 : public Tester {
public:
void Validate() override {
std::cout << "Test1 Validate" << std::endl;
Tester::Validate();
}
};
class Test2 : public Test1 {
public:
void Validate() override {
std::cout << "Test2 Validate" << std::endl;
Test1::Validate();
}
};
void RunTest(Tester& test) {
test.Validate();
}
int main() {
Test2 t;
RunTest(t);
}
在神霹雳上测试一下!