子类中的方法不会被父类中的虚函数调用

Method in child class doesn't get called by virtual function in parent class

本文关键字:父类 函数调用 方法 子类      更新时间:2023-10-16

在我的应用程序中,我有几个读取器,它们从.csv文件中读取数据。我现在想通过为那些拥有方法getData(std::string filename)的读者创建一个父类来构建它们。我想通过在基类中实现一个虚拟方法来实现它。文件名应由构造函数传递。

int main()
{
std::string filename = "file.csv";
ChildReader1 reader = new ChildReader1(filename);
}

ChildReader1.h

class ChildReader1: public ParentReader
{
public:
ChildReader1(std::string filename)
: ParentReader(filename)
{
};
void getData(std::string filename)
{
//get the data here
}
};

ParentReader.h

class ParentReader
{
public:
ParentReader() {};
ParentReader(std::string filename)
{
getData(filename);
};
~ParentReader() {};
virtual void getData(std::string filename) {};
};

目前,filename被传递给ParentReader,但getData(filename)打开了ParentReader中的虚拟方法,而不是ChildReader1中的实际方法。我该如何解决?

您可以通过工厂解决问题:

class ParentReader
{
public:
virtual ~ParentReader() = default
virtual void getData(const std::string& filename) = 0;
};
template <typename T, typename ... Ts>
std::unique_ptr<T> MakeReader(const std::string& filename, Ts&&... args)
{
static_assert(std::is_base_of<ParentReader, T>::value, "!");
auto res = std::make_unique<T>(std::forward<Ts>(args)...);
res->getData(filename);
return res;
}

在构造基类(父类)期间不会调用虚拟方法。因为派生的还没有准备好。

12.7结构和破坏[Cditor]#4 ISO/IEC N3797

成员函数,包括虚拟函数(10.3),可以在构造或销毁(12.6.2)期间调用。当从构造函数或析构函数直接或间接调用虚拟函数时,包括在类的非静态数据成员的构造或销毁期间,调用所应用的对象是正在构造或销毁的对象(调用它x),调用的函数是构造函数或析构函数类中的最后一个重写器,而不是在更派生的类中重写它。如果虚拟函数调用使用显式类成员访问(5.2.5),并且对象表达式引用x的完整对象或该对象的基类子对象之一,而不是x或其基类子对象之一来,则行为未定义。

正如其他人所提到的,您不能在基类构造函数中的派生类上调用虚拟方法,因为派生类还没有准备好。

一种解决方案是在ChildReader1:上具有工厂功能

class ParentReader {
public:
ParentReader(){};
void initialize(const std::string& filename){
getData(filename);
};
virtual ~ParentReader(){};
virtual void getData(const std::string& /*filename*/) {
};
};
class ChildReader1 : public ParentReader {
private:
ChildReader1(){}
public:
void getData(const std::string& /*filename*/) override {
// get the data here
}
static std::unique_ptr<ChildReader1> create(const std::string &filename) {
auto reader = std::unique_ptr<ChildReader1>(new ChildReader1);
reader->initialize(filename);
return reader;
}
};
int main() {
std::string filename = "file.csv";
auto reader = ChildReader1::create(filename);
}

工厂函数创建一个完整的对象,然后可以在返回之前调用虚拟函数。您可以将对象的构造函数设为私有,以强制调用方使用您的工厂函数。

为了避免派生类之间的代码重复,可以引入一个中间CRTP类。

正如其他人所说,您不应该在构造函数中调用虚拟函数。如果你想想正在发生的事情,原因很简单:

调用ChildConstructor->调用ParentConstructor->创建Parent->调用getData->创建Child

在调用getData时,唯一存在的对象是Parent,因此它不可能调用Child的getData。