C++ C+的问题+;抽象类(我可以用Java实现,但不能用C+;+;!)
首先,我搜索了这个问题,发现了很多类似的问题,但我找不到解决问题的答案。如果这只是我的愚蠢,我很抱歉 我想做的是让抽象类的构造函数调用一个纯虚拟的函数。在Java中,这是可行的,因为子类提供了被调用的抽象方法的实现。但是,在C++中,我得到了这个链接错误:C++ C+的问题+;抽象类(我可以用Java实现,但不能用C+;+;!),c++,subclass,abstract,pure-virtual,C++,Subclass,Abstract,Pure Virtual,首先,我搜索了这个问题,发现了很多类似的问题,但我找不到解决问题的答案。如果这只是我的愚蠢,我很抱歉 我想做的是让抽象类的构造函数调用一个纯虚拟的函数。在Java中,这是可行的,因为子类提供了被调用的抽象方法的实现。但是,在C++中,我得到了这个链接错误: test.o:test.cpp:(.text$_ZN15MyAbstractClassC2Ev[MyAbstractClass::MyAbstractClass ()]+0x16): undefined reference to `MyAbs
test.o:test.cpp:(.text$_ZN15MyAbstractClassC2Ev[MyAbstractClass::MyAbstractClass
()]+0x16): undefined reference to `MyAbstractClass::initialize()'
collect2: ld returned 1 exit status
这是我的密码:
#include <iostream>
class MyAbstractClass {
protected:
virtual void initialize() = 0;
public:
MyAbstractClass() {
initialize();
}
};
class MyClass : public MyAbstractClass {
private:
void initialize() {
std::cout << "yey!" << std::endl;
}
};
int main() {
MyClass *my = new MyClass();
return 0;
}
这个代码打印“Yey!”。非常感谢任何帮助 正如@Seth所解释的,您不能在构造函数中调用虚拟函数。更具体地说,虚拟调度机制在构建和销毁期间被禁用。要么使您的
初始化成员函数为非虚拟函数,并在基类中实现它,要么让用户显式调用它。让我在这里引用Scott Meyers(请参阅):
第9项:在构造或销毁期间,切勿调用虚拟函数
我将从概述开始:在构造或销毁期间不应该调用虚拟函数,因为这些调用不会按照您的想法执行,如果它们执行了,您仍然会不高兴。如果你是一个正在恢复的java或C程序员,请密切注意这个项目,因为这是一个地方,那些语言Zigg,而C++ +ZAG.<
问题:在对象构造期间,虚拟函数表可能尚未准备就绪。想象一下,你的类在继承方面排名第四。构造函数是按继承顺序调用的,因此在调用这个纯虚拟(或者即使它是非纯的)时,您希望基类为尚未完成的对象调用initialize
MyAbstractClass() {
initialize();
}
它不会执行到MyClass::initialize()
的虚拟分派,因为在对象构造的这个阶段,尚未创建其MyClass
部分。因此,您实际上是在调用MyAbstractClass::initialize()
,因此必须对其进行定义。(是的,可以定义纯虚拟成员函数。)
尽量避免从构造函数调用虚拟成员函数,因为这类事情会发生,并且会让您措手不及。这样做很少有意义
另外,尽量避免使用initialize()
函数;您已经有了可以使用的构造函数
更新
实际上,尽管您可以将上述内容视为对任何其他虚拟成员函数的读取,但从构造函数调用纯虚拟成员函数会产生未定义的行为。所以不要尝试在C++中,不能从构造函数或析构函数调用纯虚函数(即使它有定义)。如果调用非纯函数,那么它将被调度,就像对象的类型是正在构造的类一样,因此您永远无法调用派生类中定义的函数
在这种情况下,你不需要;派生类的构造函数将在基类的构造函数之后调用,因此您可以通过以下方式获得所需的结果:
#include <iostream>
class MyAbstractClass {
public:
MyAbstractClass() {
// don't do anything special to initialise the derived class
}
};
class MyClass : public MyAbstractClass {
public:
MyClass() {
std::cout << "yey!" << std::endl;
}
};
int main() {
MyClass my;
return 0;
}
#包括
类MyAbstractClass{
公众:
MyAbstractClass(){
//不要做任何特殊的事情来初始化派生类
}
};
类MyClass:公共MyAbstractClass{
公众:
MyClass(){
std::cout在构造和销毁期间,为正在构造或销毁的基本子对象适当地设置虚拟表。这在理论上是正确的,因为派生类不是活动的(其生存期尚未开始或已经结束)C++的边已经被其他的答案处理了,但是我想在它的java方面加一个注释。从构造函数<强>调用一个虚函数是< /强>,在所有的情况下,不仅仅是C++中的问题。基本上代码是想在一个尚未创建的对象上执行一个方法,这是一个错误。
不同语言中实现的两种解决方案在试图对代码所做的尝试有所不同。在C++中,决策是在构建一个基本对象时,直到派生对象的构建开始,对象的实际类型是基,这意味着将不会有动态调度。在任何时候,对象的类型都是正在执行的构造函数的类型[*]。虽然这让一些人(包括您)感到惊讶,但它为问题提供了一个合理的解决方案
[*]相反,析构函数。随着大多数派生构造函数的完成,类型也会发生变化
在Java中,对象是从最终开始的最终类型,甚至在构建完成之前。在爪哇中,正如您所演示的,调用将被调度到最终重写器(我在这里使用C++ java俚语:到执行链中的虚拟函数的最后实现)。,这可能会导致不必要的行为。例如,考虑<代码>初始化()/<代码>:的实现。
上一个程序的输出是什么?(在底部回答)
不仅仅是一个玩具示例,请考虑<代码> MyClass <代码>提供了一些在构建时设置并保存对象的整个生命周期的不变量。也许它保存了一个对数据可以被转储的记录器的引用。通过查看类,可以看到在构造函数中设置了记录器,并假定它不能。在代码中的任意位置重置:
public class MyClass extends MyAbstractClass {
Logger logger;
MyClass() {
logger = new Logger( System.out );
}
void initialize() {
logger.debug( "Starting initialization" );
}
}
您现在可能看到了这一点。通过查看MyClass
的实现,似乎没有任何错误。logger
在构造函数中设置,因此可以在类的所有方法中使用。现在的问题是,如果MyAbstractClass
调用被调度的虚拟函数然后应用程序将因NullPointerException而崩溃
现在我希望你理解和重视C++的不执行动态调度的决定,从而避免执行。
public class MyClass extends MyAbstractClass {
final int k1 = 1;
final int k2;
MyClass() {
k2 = 2;
}
void initialize() {
System.out.println( "Constant 1 is " + k1 + " and constant 2 is " + k2 );
}
}
public class MyClass extends MyAbstractClass {
Logger logger;
MyClass() {
logger = new Logger( System.out );
}
void initialize() {
logger.debug( "Starting initialization" );
}
}