对vtable的未定义引用 当我构建我的C++程序时,我得到了错误信息

对vtable的未定义引用 当我构建我的C++程序时,我得到了错误信息,c++,gcc,g++,C++,Gcc,G++,对“vtable”的未定义引用 这个问题的原因是什么?我怎么修理它 碰巧我得到了以下代码的错误,所讨论的类是CGameModule。我一生都无法理解问题所在。起初,我认为这与忘记给一个虚拟的身体赋予功能有关,但据我所知,一切都在这里。继承链有点长,但下面是相关的源代码。我不确定我还应该提供什么信息 注意:构造函数似乎是发生此错误的地方 我的代码: class CGameModule : public CDasherModule { public: CGameModule(Dasher::C

对“vtable”的未定义引用

这个问题的原因是什么?我怎么修理它

碰巧我得到了以下代码的错误,所讨论的类是CGameModule。我一生都无法理解问题所在。起初,我认为这与忘记给一个虚拟的身体赋予功能有关,但据我所知,一切都在这里。继承链有点长,但下面是相关的源代码。我不确定我还应该提供什么信息

注意:构造函数似乎是发生此错误的地方

我的代码:

class CGameModule : public CDasherModule {
 public:
  CGameModule(Dasher::CEventHandler *pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
  : CDasherModule(pEventHandler, pSettingsStore, iID, 0, szName)
  { 
      g_pLogger->Log("Inside game module constructor");   
      m_pInterface = pInterface; 
  }

  virtual ~CGameModule() {};

  std::string GetTypedTarget();

  std::string GetUntypedTarget();

  bool DecorateView(CDasherView *pView) {
      //g_pLogger->Log("Decorating the view");
      return false;
  }

  void SetDasherModel(CDasherModel *pModel) { m_pModel = pModel; }


  virtual void HandleEvent(Dasher::CEvent *pEvent); 

 private:



  CDasherNode *pLastTypedNode;


  CDasherNode *pNextTargetNode;


  std::string m_sTargetString;


  size_t m_stCurrentStringPos;


  CDasherModel *m_pModel;


  CDasherInterfaceBase *m_pInterface;
};
继承自

class CDasherModule;
typedef std::vector<CDasherModule*>::size_type ModuleID_t;

/// \ingroup Core
/// @{
class CDasherModule : public Dasher::CDasherComponent {
 public:
  CDasherModule(Dasher::CEventHandler * pEventHandler, CSettingsStore * pSettingsStore, ModuleID_t iID, int iType, const char *szName);

  virtual ModuleID_t GetID();
  virtual void SetID(ModuleID_t);
  virtual int GetType();
  virtual const char *GetName();

  virtual bool GetSettings(SModuleSettings **pSettings, int *iCount) {
    return false;
  };

 private:
  ModuleID_t m_iID;
  int m_iType;
  const char *m_szName;
};
您确定CDasherComponent具有析构函数的主体吗?它肯定不在这里-问题是它是否在.cc文件中。 从样式的角度来看,CDasherModule应该显式定义其析构函数virtual。 看起来CGameModule在}之后的末尾有一个额外的};//为了班级。 CGameModule是否与定义CDasherModule和CDasherComponent的库相链接? 上有一个条目:

解决方案是确保定义所有非纯虚拟方法。请注意,必须定义析构函数,即使它被声明为纯虚拟[class.dtor]/7

因此,您需要提供虚拟析构函数的定义:

virtual ~CDasherModule()
{ }

所以,我发现了这个问题,这是一个糟糕的逻辑和不完全熟悉汽车制造/自动工具世界的组合。我将正确的文件添加到Makefile.am模板中,但我不确定构建过程中的哪个步骤实际创建了Makefile本身。所以,我是用一个旧的makefile编译的,它根本不知道我的新文件


感谢您的回复和GCC常见问题的链接。我一定会读到,为了避免这个问题出于真正的原因而发生。

不管怎样,忘记虚拟析构函数上的实体会产生以下结果:

对“CYourClass的vtable”的未定义引用


我添加了一个注释,因为错误消息具有欺骗性。这是在gcc版本4.6.3中出现的。

未定义的vtable引用也可能由于以下情况而出现。试试这个:

A类包括:

virtual void functionA(parameters)=0; 
virtual void functionB(parameters);
B类包括:

virtual void functionA(parameters)=0; 
virtual void functionB(parameters);
上述函数的定义。 上述函数的定义b。 类C包含:现在您正在编写一个类C,在其中您将从类a派生它

现在,如果您尝试编译,您将得到对C类vtable的未定义引用作为错误

原因:

functionA定义为纯虚拟,其定义在B类中提供。 functionB被定义为虚拟的而不是纯虚拟的,所以它试图在类A中找到它的定义,但您在类B中提供了它的定义

解决方案:

如果您有这样的需求,请将函数B设置为纯虚拟函数 虚空函数参数=0; 这是可行的,它是经过测试的 为类A中的函数B提供定义,并将其保持为虚拟函数。 希望它能起作用,因为我没试过
我只是因为.cpp文件不在makefile中而出现这个错误


通常,如果您忘记编译或链接到包含定义的特定对象文件,您将遇到此错误。

如果所有其他操作都失败,请查找重复。在我读到另一篇文章中的引用之前,我一直被对构造函数和析构函数的明确的初始引用误导。这是任何未解决的方法。在我的例子中,我认为我已经将使用char*xml作为参数的声明替换为使用不必要的麻烦const char*xml的声明,但是,我创建了一个新的声明,并保留了另一个声明。

不跨越post,而是。如果你处理的是继承问题,那么谷歌的第二次点击就是我错过的,也就是说,应该定义所有虚拟方法

例如:

virtual void fooBar() = 0;

有关详细信息,请参见answare。刚刚意识到上面已经提到了它,但它可能会帮助某些人。

如果您正在使用Qt,请尝试重新运行qmake。如果此错误在小部件的类中,qmake可能没有注意到应该重新生成ui类vtable。这就解决了这个问题。

< P>编译程序,GNU C++编译器必须决定放在VTABLE的位置,以防你定义了一个对象在多个编译单元上的虚拟函数,例如,一些对象的虚函数定义是在.CPP文件中的,其他的是在CPP文件中,等等。p> 编译器选择将vtable放在定义第一个声明的虚拟函数的位置

现在,如果您出于某种原因忘记为对象中声明的第一个虚拟函数提供定义,或者错误地忘记在链接阶段添加编译后的对象,您将得到此错误

作为一个副作用,请注意,仅对于这个特定的虚拟函数,您不会
我不能像丢失函数foo一样得到传统的链接器错误。

我刚刚遇到了另一个导致此错误的原因,您可以检查一下

基类将纯虚函数定义为:

而这个子类

int foo(int x) override;
问题在于输入错误,=0应该在括号外:

virtual int foo(int x) = 0;

因此,如果你向下滚动这么远,你可能找不到答案-这是需要检查的其他内容。

这里有很多关于各种答案的推测。下面我将给出一个相当小的代码来重现这个错误,并解释它发生的原因

复制此错误的代码非常少

IBase.hpp

1.hpp

导出的.cpp

myclass.cpp

现在可以通过删除IBase.hpp中的=0来重现错误。我得到这个错误:

~/.../catkin_ws$ g++ -std=c++11 -o /tmp/m.out /tmp/myclass.cpp /tmp/Derived.cpp
/tmp/cclLscB9.o: In function `IBase::IBase(IBase const&)':
myclass.cpp:(.text._ZN5IBaseC2ERKS_[_ZN5IBaseC5ERKS_]+0x13): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o: In function `IBase::IBase()':
Derived.cpp:(.text._ZN5IBaseC2Ev[_ZN5IBaseC5Ev]+0xf): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o:(.rodata._ZTI7Derived[_ZTI7Derived]+0x10): undefined reference to `typeinfo for IBase'
collect2: error: ld returned 1 exit status
解释

请注意,上面的代码不需要任何虚拟析构函数、构造函数或任何其他额外的文件来成功编译,尽管您应该拥有它们

理解此错误的方法如下: 链接器正在寻找IBase的构造函数。这将需要它作为派生的构造函数。但是,由于派生重写了来自IBase的方法,它附带了将引用IBase的vtable。当链接器说IBase对vtable的引用未定义时,它基本上意味着Derived对IBase有vtable引用,但它找不到IBase的任何编译目标代码可供查找。所以归根结底,类IBase有声明,但没有实现。这意味着IBase中的方法被声明为virtual,但我们忘记将其标记为纯virtual或提供其定义

分离端

如果所有其他方法都失败了,那么调试这个错误的一种方法就是编译最小的程序,然后不断更改它,使它达到您想要的状态。在此期间,继续编译以查看它何时开始失败

关于ROS和柳絮构建系统的说明

如果您是使用catkin构建系统在ROS中编译上述一组类,则需要在CMakeLists.txt中使用以下行:

add_executable(myclass src/myclass.cpp src/Derived.cpp)
add_dependencies(myclass theseus_myclass_cpp)
target_link_libraries(myclass ${catkin_LIBRARIES})

第一行基本上是说,我们希望生成一个名为myclass的可执行文件,下面的文件可以找到构建该文件的代码。其中一个文件应该有主文件。请注意,您不必在CMakeLists.txt中的任何位置指定.hpp文件。另外,您不必将Derived.cpp指定为库。

所以我在Windows XP和MinGW编译器中使用了Qt,这件事让我抓狂

基本上,moc_xxx.cpp即使在添加我时也是空的

Q_对象

删除所有使函数虚拟化、显式化和任何你猜测的东西都不起作用。最后我开始一行一行地删除,结果是我

#ifdef something
围绕着文件。即使ifdef为true,也不会生成moc文件

因此,删除所有ifdef修复了问题


Windows和VS 2013没有出现这种情况。

在我的例子中,我使用的是Qt,并在foo.cpp not.h文件中定义了一个QObject子类。修复方法是在foo.cpp的末尾添加include foo.moc。

我认为还值得一提的是,当您尝试链接到至少有一个虚拟方法的任何类的对象,并且链接器找不到该文件时,您也会收到消息。 例如:

Foo.hpp:

class Foo
{
public:
    virtual void StartFooing();
};
Foo.cpp:

#include "Foo.hpp"

void Foo::StartFooing(){ //fooing }
汇编时使用:

g++ Foo.cpp -c
和main.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}
汇编并链接到:

g++ main.cpp -o main
给出了我们最喜欢的错误:

/tmp/cclKnW0g.o:在函数main:main.cpp:.text+0x1a:未定义 对Foo'collect2的vTable的引用:错误:ld返回了1个出口 地位

这是因为我的无知:

Vtable是在编译时为每个类创建的

链接器无权访问Foo.o中的vtable

TL;DR-解释vtable丢失的原因以及如何修复。答案很长,因为它解释了为什么编译器可能忘记创建vtable。编辑

什么是vtable? 在尝试修复错误消息之前,了解错误消息的含义可能会很有用。我将从一个高层次开始,然后深入到更多细节。这样,一旦人们对vtables的理解感到满意,他们就可以跳过前面的内容…现在有一大群人在前面蹦蹦跳跳对于那些在附近逗留的人:

vtable基本上是最常见的实现。当使用vtable时,每个多态类在程序中的某个地方都有一个vtable;您可以将其视为类的隐藏静态数据成员。多态类的每个对象都与其最派生类的vtable相关联。通过检查此关联,程序可以发挥其多态魔力。重要提示:vtable是一个实现细节。它不是由C++标准规定的,即使大多数都是这样吗?C++编译器使用VTABLE实现多态行为。我所介绍的细节要么是典型的,要么是合理的。编译器被允许偏离这一点

每个多态obje 对于对象最派生的类,ct有一个指向vtable的隐藏指针,在更复杂的情况下可能有多个指针。通过查看指针,程序可以判断对象的真实类型,除了在构造期间,但让我们跳过这个特殊情况。例如,如果类型A的对象不指向A的vtable,则该对象实际上是从A派生的某个对象的子对象

vtable的名称来自虚拟函数表。它是一个存储指向虚拟函数的指针的表。编译器选择表格布局的约定;一种简单的方法是按照虚函数在类定义中声明的顺序遍历虚函数。调用虚拟函数时,程序会跟随对象指向vtable的指针,转到与所需函数关联的条目,然后使用存储的函数指针调用正确的函数。有各种各样的技巧可以使这项工作顺利进行,但我在这里不赘述

在何处/何时生成vtable? vtable是自动生成的,有时由编译器发出。编译器可以在看到多态类定义的每个翻译单元中发出vtable,但这通常是不必要的过度使用。另一种方法是选择一个单独的翻译单元来放置vtable,类似于选择一个单独的源文件来放置类的静态数据成员。如果此选择过程未能选择任何翻译单位,则vtable将成为未定义的引用。因此出现了错误,其信息显然不是特别清楚

类似地,如果选择过程确实选择了翻译单元,但该对象文件没有提供给链接器,那么vtable将成为未定义的引用。不幸的是,与选择过程失败的情况相比,这种情况下的错误消息可能更不清楚。感谢提到这种可能性的回答者。否则我可能会忘记的

如果我们从为每个需要一个源文件来实现的类提供一个源文件的传统开始,那么gcc使用的选择过程是有意义的。在编译源文件时发出vtable会很好。让我们称之为我们的目标。然而,即使不遵循这一传统,遴选过程也需要发挥作用。因此,与其寻找整个类的实现,不如让我们寻找该类的特定成员的实现。如果遵循传统——如果该成员事实上得到了落实——那么这就实现了目标

gcc和其他编译器可能选择的成员是第一个非纯虚拟的非内联虚拟函数。如果您是在其他成员函数之前声明构造函数和析构函数的人群中的一员,那么该析构函数很有可能被选中。你记得把析构函数变成虚拟的,对吗?也有例外;我认为最常见的异常是当为析构函数提供内联定义时,以及当使用=default请求默认析构函数时

精明的人可能会注意到,允许多态类为其所有虚拟函数提供内联定义。这不会导致选择过程失败吗?在较旧的编译器中确实如此。我读过最新的编译器已经解决了这个问题,但我不知道相关的版本号。我可以试着查找这个,但是编写代码或者等待编译器抱怨更容易

总之,对vtable错误的未定义引用有三个主要原因:

成员函数缺少其定义。 未链接对象文件。 所有虚拟函数都有内联定义。 这些原因本身不足以单独导致错误。相反,这些是您解决错误的方法。不要期望故意制造这些情况中的一种肯定会产生这种错误;还有其他要求。希望解决这些情况将解决此错误

好的,当被问到这个问题时,第三个可能已经足够了

如何修复错误? 欢迎回到前面跳的人

看看你的类定义。查找第一个非纯虚拟not=0且其定义为not=default的非内联虚拟函数。 如果没有这样的函数,请尝试修改您的类,使其存在。错误可能已解决。 另请参阅以获取警告。 查找该函数的定义。如果缺少,请添加它!错误可能已解决。 另请参阅以获取警告。 检查链接命令。如果它没有提到带有该函数定义的对象文件,请修复它!错误可能已解决。 对每个虚拟函数重复步骤2和步骤3,然后对每个非虚拟函数重复步骤2和步骤3,直到错误得到解决。如果仍然卡滞,请对每个站点重复此操作 c数据成员。 范例 做什么的细节可能会有所不同,有时会分为不同的问题,如。不过,我将提供一个例子,说明在可能让新程序员困惑的特定情况下应该做什么

第1步提到修改类,使其具有特定类型的函数。如果对该功能的描述超出了你的理解,那么你可能正处于我想要解决的情况。记住,这是实现目标的一种方式;这不是唯一的方法,在你的具体情况下,很容易会有更好的方法。让我们调用类A。在类定义中是否将析构函数声明为

virtual ~A() = default;

??如果是这样,两个步骤将把析构函数更改为我们想要的函数类型。首先,将该行更改为

virtual ~A();
其次,将以下行放在作为项目一部分的源文件中,最好是包含类实现的文件(如果有):

A::~A() {}

这使得您的虚拟析构函数非内联并且不是由编译器生成的。请随意修改,以更好地匹配您的代码格式样式,例如在函数定义中添加标题注释。

我尝试了所有详细步骤,但仍然被此错误难倒。经过大量的头部撞击后,我终于明白了。我很粗心。您应该能够通过下面的示例代码重现这个令人痛苦的错误

[jaswantp@jaswant-拱门建造]$gcc-v 使用内置规格。 收集\u GCC=GCC COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/LTO-WRAPPER 目标:x86_64-pc-linux-gnu 配置为:/build/gcc/src/gcc/configure-prefix=/usr-libdir=/usr/lib-libexecdir=/usr/lib-mandir=/usr/share/man-infodir=/usr/share/info-with bugurl=https://bugs.archlinux.org/ -启用语言= C、C++、艾达、FORTRAN、GO、LTO、Objc、Obj-C++,d-使用isl-使用链接器哈希样式=gnu-使用系统zlib-启用-启用cxa-启用atexit-启用cet=自动启用检查=发布-启用clocale=gnu-启用默认饼图-启用默认ssp-启用gnu间接函数-启用gnu唯一对象-启用安装liberty-启用链接器构建id-启用lto-启用多库-启用插件-启用共享-enable threads=posix-disable libssp-disable libstdcxx pch-disable libunwind exceptions-disable werror gdc_include_dir=/usr/include/dlang/gdc 线程模型:posix 支持的LTO压缩算法:zlib zstd gcc版本10.2.0 gcc //CelesetialBody.h 类天体{ 公众: 虚拟空白打印; 受保护的: 天星体; 虚拟天体; }; //CelestialBody.cpp 包括CelestialBody.h CelestialBody::CelestialBody{} CelestialBody::~CelestialBody=默认值; void CelestialBody::Print{} //行星 包括CelestialBody.h 行星类别:公共天体 { 公众: 无效打印覆盖; 受保护的: 行星 ~行星覆盖; }; //Planet.cpp 包括行星 行星:{}行星 行星::~行星{} void Print{}//故意忘记在'Planet:'前面加前缀:` 我们得到了我们最喜欢的错误

$mkdir构建 $cd构建 $cmake。。 $make [50%]建造目标天体 目标行星的扫描相关性 [75%]构建CXX对象cmakFiles/Planet.dir/Planet.cpp.o [100%]链接CXX共享库libPlanet.so /usr/bin/ld:CMakeFiles/Planet.dir/Planet.cpp.o:在函数“Planet::Planet”中: Planet.cpp:.text+0x1b:对“行星vtable”的未定义引用 /usr/bin/ld:CMakeFiles/Planet.dir/Planet.cpp.o:在函数“Planet::~Planet”中: Planet.cpp:.text+0x3d:对“行星vtable”的未定义引用 collect2:错误:ld返回了1个退出状态 make[2]:***[CMakeFiles/Planet.dir/build.make:104:libPlanet.so]错误1 make[1]:***[CMakeFiles/Makefile2:97:CMakeFiles/Planet.dir/all]错误2 make:**[Makefile:103:all]错误2 当然,我在Planet.cpp中所做的事情应该用这个技巧来解决

看看你的类定义。查找第一个非纯虚拟not=0且其定义为not=default的非内联虚拟函数。 从答案来看。 如果还有其他人尝试了以上所有方法,但没有任何效果,可能您也像我一样,不小心忘记了将::前缀添加到一个或多个成员函数


要么我需要检查眼睛,要么我需要睡觉。

对于使用CMakeList.txt的Qt用户

在您的CMakeLists.txt中添加此行:在


如果您忘记moc一个标题,您会得到这个错误

我在尝试实现抽象工厂模式时也遇到了这个问题,但是忘记链接一些库。因此,如果没有任何帮助,请检查是否链接了所有必需的库

-是的,CDasherComponent在cpp中有析构函数体。当我发布这篇文章时,我以为它是在.h中声明的。-正式注明这是我在剥离文档时错误添加的额外括号。-据我所知,是的。我一直在修改一个我没有编写的automake文件,但我一直在遵循那些在其他类中使用相同继承模式的模式,这些模式来自同一个c

lasses,所以除非我犯了一个完全可能犯的愚蠢错误,否则我认为这不是问题。@RyanG:尝试将所有虚拟函数定义移到类定义中。确保它们都在那里,看看结果是否有变化。我完全没有注意到错误消息指定了一个函数。它恰好是构造函数,所以我看到了我的类名,没有建立连接。所以,构造函数抛出了这个。我将把这个细节添加到我的原始帖子中。如果您在进行重大更改(例如qmake-project,然后qmake生成新的Makefile)后没有重建项目文件,这可能是使用Qt.@DavidC.Rankin时出现错误的原因,另一个与Qt相关的问题是,如果从外部复制带有Q_对象的文件,但它还不是.pro文件的一部分,所以尽管它编译得很好,但它没有链接。我们必须将该.h/.cpp文件添加到.pro文件中才能进行qmake。简而言之,.cpp只是没有包含在构建中。这个错误消息确实有误导性。对于Qt用户来说:如果你忘了moc一个标题,你会得到同样的错误。不过我认为你应该接受Alexandre Hamez的答案。搜索此错误的人很可能需要他的解决方案,而不是您的。-1这可能是您问题的解决方案,但不是原始问题的答案。正确的答案很简单,您没有提供包含所需符号的对象文件。你为什么没能提供它们是另一回事。@Walter:事实上这正是我想要的答案。其他方法是显而易见的,因此unhelp.nm-CGameModule.o | grep CGameModule::将列出定义的方法,假设您的整个类实现都进入逻辑对象文件。您可以将其与定义为虚拟的内容进行比较,以找出您遗漏了什么。FFS,为什么编译器不检查它并打印错误消息?显然,这只能由链接器发现,而不是由编译器发现。在我的例子中,我们有一个没有析构函数实现的抽象类。我必须将空的实现~MyClass{}放入存档libxyz中,当您试图链接的对象丢失时,您可能会遇到类似这样的错误。文件:未定义对“vtable for objfilename”的引用我必须将空的虚拟析构函数体显式放入定义文件*.cc中。在头文件中添加虚拟析构函数仍然会给我带来错误。请注意,一旦我将虚拟析构函数添加到实现文件中,gcc就会告诉我实际的错误,这是另一个函数中缺少的主体。@PopcornKing我也看到了同样的问题。甚至定义~Destructor=default;在头文件中没有帮助。是否有针对gcc的文件缺陷?这可能是另一个问题,但我的问题是没有非虚拟析构函数的实现正在切换到唯一/共享指针并将其从源文件中删除,但在Header中没有实现这解决了我的问题,添加了一个空{}虚拟析构函数的主体避免了错误。我刚刚删除了整个文件夹,构建它也起作用。这不是qmake所独有的,我对cmake也有同样的功能。问题的一部分可能是这两个工具的头文件都有点问题,这可能并不总是在需要时触发重建。。。显然不是。我确实按照你的建议运行了qmake,然后重新构建,它解决了我的问题。注释掉Q_对象行使我的简单测试应用程序构建了一个普通的g++*.cpp。。。。需要一些快速而肮脏的东西,但qmake充满了悲伤。@ilya1725您不仅修复了格式之类的问题,还更改了答案,例如说C类是从B而不是A派生的,您正在更改第二个解决方案。这大大改变了答案。在这些情况下,请给作者留下评论。谢谢大家!@FabioTurati classC继承了什么类?这个句子不清楚。还有,C类的含义是什么:?@ilya1725这个答案不是很清楚,我并不反对编辑和改进它。我想说的是,你的编辑改变了答案的含义,这是一个太剧烈的改变。希望作者能介入并澄清他的意思,尽管他已经很长时间没有活动了。谢谢!在我的例子中,我的层次结构中只有两个类。类A声明了一个纯虚方法。类B的声明声明它将重写此方法,但我还没有编写被重写方法的定义。我只是在虚拟方法中添加了=0,现在它可以工作了。谢谢事实上,当涉及虚拟事物时,消息似乎与通常未定义的{function/class/struct}引用略有不同。把我甩了。哦,好极了!对于非常详细和范围非常好的解释。必须向下滚动到很远的地方才能阅读。请投票支持这个出色的解释!谢谢,将构造函数/析构函数的定义从标题转换为CPP解决了C++。 很多不小心射中自己脚的方法。这看起来像是我很可能犯的错误。
g++ Foo.cpp -c
#include "Foo.hpp"

int main()
{
    Foo foo;
}
g++ main.cpp -o main
virtual ~A() = default;
virtual ~A() {}
virtual ~A();
A::~A() {}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project (space_engine)
add_library (CelestialBody SHARED CelestialBody.cpp)
add_library (Planet SHARED Planet.cpp)
target_include_directories (CelestialBody PRIVATE ${CMAKE_CURRENT_LIST_DIR})  
target_include_directories (Planet PRIVATE ${CMAKE_CURRENT_LIST_DIR})    
target_link_libraries (Planet PUBLIC CelestialBody)

# hardened linker flags to catch undefined symbols
target_link_options(Planet 
    PRIVATE 
    -Wl,--as-needed
    -Wl,--no-undefined
)