0

When building my C++ program, I'm getting the error message.

undefined reference to vtable

I have two virtual abstract classes called and I can't quite figure out what I'm doing wrong if I'm doing anything wrong. I'm getting errors from both of the classes that are inheriting from the abstract class. My abstract class is

undefined reference to `vtable for hittable_list'

undefined reference to `vtable for sphere'

hittable.h

     #ifndef HITTABLE_H
        #define HITTABLE_H
    
    #include "ray.h"
    
    struct hit_record {
        hit_record() {}
        ~hit_record() {}
        float t;
        vecfloat p;
        vecfloat normal;
        float MAXFLOAT = 100.0; 
}; 

    //Abstract Class containing Sphere and hittablelist 
    class hittable 
    {
     public:
            virtual ~hittable() = 0;
        
            virtual bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const = 0;
 };
        
        #endif

Classes inherting from my abstract class are.

sphere.h

 #ifndef SPHERE_H
#define SPHERE_H

#include "hittable.h"

class sphere : public hittable
{
public:
    sphere() {}
    ~sphere() {}
    sphere(vecfloat cen, float r) : center(cen), radius(r) {}
    bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const;

protected:
    vecfloat center;
    float radius;
};

#endif

sphere.cc

#include "include/sphere.h"

bool sphere::hit(const ray &r, float t_min, float t_max, hit_record &rec) const
{
    vecfloat oc = r.origin() - center;
    float a = oc.dot_product(r.direction());
    float b = oc.dot_product(oc) - radius * radius;
    float c = oc.dot_product(oc) - radius * radius;
    float discriminant = b * b - a * c;
    if (discriminant > 0)
    {
        float temp = (-b - sqrt(b * b - a * c)) / a;
        if (temp < t_max && temp > t_min)
        {
            rec.t = temp;
            rec.p = r.point_at_parameter(rec.t);
            rec.normal = (rec.p - center) / radius;
            return true;
        }
        temp = (-b + sqrt(b * b - a * c)) / a;
        if (temp < t_max && temp > t_min)
        {
            rec.t = temp;
            rec.p = r.point_at_parameter(rec.t);
            rec.normal = (rec.p - center) / radius;
            return true;
        }
    }
    return false;
}

hittable.h

#ifndef HITTABLELIST_H
#define HITTABLELIST_H
#include "hittable.h"

class hittable_list : public hittable
{
public:
    hittable_list() {}

    hittable_list(hittable **l, int n)
    {
        list = l;
        list_size = n;
    }
    bool hit(const ray &r, float t_min, float t_max, hit_record &rec) const;
    ~hittable_list() {}

protected:
    hittable **list;
    int list_size;
};

#endif

hittable.cc

#include "include/hittablelist.h"

bool hittable_list::hit(const ray &r, float t_min, float t_max, hit_record &rec) const
{

    hit_record temp_rec;
    auto hit_anything = false;
    auto closet_so_far = t_max;
    for (int i = 0; i < list_size; i++)
    {
        if (list[i]->hit(r, t_min, closet_so_far, temp_rec))
        {
            hit_anything = true;
            closet_so_far = temp_rec.t;
            rec = temp_rec;
        }
    }
    return hit_anything;
}
rioV8
  • 24,506
  • 3
  • 32
  • 49
Justin
  • 1
  • 1
  • 4
  • I've never even thought to make a pure virtual destructor. Wonder what happens and it it's related. – user4581301 Aug 22 '20 at 03:56
  • Yeah, that'll be it. Lemme shrink the code down to a [mre] and write it up. – user4581301 Aug 22 '20 at 03:59
  • Huh.Can't reproduce the undefined `vtable` – user4581301 Aug 22 '20 at 04:06
  • Does this answer your question? [Undefined reference to vtable](https://stackoverflow.com/questions/3065154/undefined-reference-to-vtable), specifically the answers that mention checking your link command. – JaMiT Aug 22 '20 at 04:09
  • @JaMiT That will do it, but mostly by accident.The kicker here is the `hittable` destructor must be defined to solve all of the problems with this code. It can stay pure `virtual`, but it must be defined. Classes derived from it must have something to call. – user4581301 Aug 22 '20 at 04:13
  • @user4581301 I don't see why the destructor should be pure virtual since the class is already abstract. Making it just virtual should suffice in this case. EDIT: nevermind, it could be useful when you want derived classes to explicitly define their destructors – Alexey Andronov Aug 22 '20 at 04:18
  • @AlexeyAndronov There's no point to it here and would only be useful in edge cases where the you cannot allow the base class to be instantiated and the base class contains no pure virtual member functions. I can't think of a case where I would use this, but doesn't mean I'm just not being imaginative enough. – user4581301 Aug 22 '20 at 04:32

1 Answers1

1

Solution

Change

virtual ~hittable() = 0; 

into

virtual ~hittable() = default; 

or

virtual ~hittable()
{
    // does nothing
} 

The destructor can remain pure virtual, but it must have a definition.

hittable::~hittable()
{
    // does nothing
}

So what happened? We can crush the given code down to the following example (Note I can't reproduce the missing vtable with this code or the given code, but regardless, the above will fix it)

Minimal example:

class hittable
{
public:
    virtual ~hittable() = 0; // here we have a destructor, 
                             // but there's no implementation 
                             // so there is nothing for the
                             // destructors of derived classes 
                             // to call  

    virtual bool hit() const = 0;
};

class sphere: public hittable
{
public:
    bool hit() const;

};

bool sphere::hit() const
{
    return false;
}

destructors call any base class destructors, and when shpere goes to call ~hittable, it finds that while ~hittableis declared, there is no implementation. Nothing to call.

user4581301
  • 33,082
  • 7
  • 33
  • 54