Some linker errors i don't understand

Started by
6 comments, last by Martijnvdc 15 years, 8 months ago
Hello, I am trying to make a small raytracer, i already made one once but is was coded very badly. While i was trying to make the new one i got some unexpected results that i just couldn't solve... Now i tried to recreate the problem but i get linker errors which i can't solve either, here is the source code:

#include <iostream>
#include <vector>
using namespace std;


class Ray
{
public:
       float test;  
};

class Geometry
{
   public:   
   virtual float GetDistanceUntilHit(Ray &r);
};

float Geometry::GetDistanceUntilHit(Ray &r) 
{
      return 1.0f;
};
 
class Object
{          
   private:     
   Geometry* Geom;         
   public:
   Object(Geometry *geometry);
   Geometry& GetGeom(){ return *Geom; }
   Object(){}
};

class Sphere :  public Geometry
{
   private:
           
   public:  
   Sphere(){Radius=10.0f;}
   float Radius; 
   virtual float GetDistanceUntilHit(Ray &r);    
};


float Sphere::GetDistanceUntilHit(Ray &R) 
{
      return 2.0f;
};

class Scene
{
   private:
           
   public:   
   Scene(){};
   std::vector<Object*> Objects; 
   void AddObject(Object* Obj);
};
 
Scene TestScene;
int main()
{
Sphere* Sp= new Sphere();
Object* Obj= new Object(Sp);
TestScene.AddObject(Obj);
system("PAUSE");
return EXIT_SUCCESS;
}

Here are the linker errors: C:\PROJECTS\Tests\Test2\main.o(.text+0xb5) In function `main': [Linker error] undefined reference to `Object::Object(Geometry*)' [Linker error] undefined reference to `Scene::AddObject(Object*)' C:\PROJECTS\Tests\Test2\main.o(.text+0xb5) ld returned 1 exit status C:\PROJECTS\Tests\Test2\Makefile.win [Build Error] n\make.exe: *** [Test1.exe] Error 1 How could it be a undefined reference if i just defined it?? I would like to know how to solve the linker errors. The unexpected results i got when calling Sphere::GetDistanceGetDistanceUntilHit(Ray &r) it returns Geometry::GetDistanceGetDistanceUntilHit(Ray &r), so i would like to know how i can get it to work how i want it to. also, i compiled it in Dev-C++ Sorry for my bad English... Thanks you :)
Advertisement
The problem is simply that you declared some methods, and never implemented them. You need to implement them like you did for the Sphere::GetDistanceUntilHit method.

Oh and by the way...just about all of us here would recommend using Visual C++ Express over Dev-C++. Dev-C++ simply hasn't been updated in a long time, and Visual C++ outclasses it many ways.
Ah, but you didn't define it, you merely declared it. I'll address the Object constructor missing symbol, but the AddObject method is the same issue. Compare these two lines of your code:
class Object{             ...   Object(Geometry *geometry);   Object(){}   ...};


The first line declares a constructor, because it provides no body in curly brackets. This causes the compiler to generate references to the constructor, but doesn't actually create it (not even a blank one, it just completely omits it). This causes a successful compilation, but when the linker comes in and needs to match up all the references with the actual bodies, it can't, because the compiler never created Object(Geometry *geometry). And so you get your error.

So, you must be sure to define the constructor too. You can either do this directly inside the class definition:
class Object{   ...   Object(Geometry *geometry)   {      ...   }}

Or, you can do it as a separate method declaration:
Object::Object(Geometry *geometry){   ...}

This whole definition vs declaration thing makes a lot more sense once you start using headers and multiple source files.
Thank you both very much, you really helped me!
I do use header files and such but i made it in one file so you could read it easier, and i removed a lot of parts that didn't matter.
Now it compiles and links but it crashes.


#include <iostream>#include <vector>using namespace std;class Ray{public:       float test;  };class Geometry{   public:      virtual float GetDistanceUntilHit(Ray &r);};float Geometry::GetDistanceUntilHit(Ray &r) {      return 1.0f;}; class Object{             private:        Geometry* Geom;            public:   Object(Geometry *geometry){};   Geometry& GetGeom(){ return *Geom; }   Object(){}};class Sphere :  public Geometry{   private:              public:     Sphere(){Radius=10.0f;}   float Radius;    virtual float GetDistanceUntilHit(Ray &R);    };float Sphere::GetDistanceUntilHit(Ray &R) {      return 2.0f;};class Scene{   private:              public:      Scene(){};   std::vector<Object*> Objects;    void AddObject(Object* Obj);};void Scene::AddObject(Object* Obj){   Objects.push_back(Obj);                                         } Scene TestScene;int main(){Sphere* Sp= new Sphere();Object* Obj= new Object(Sp);TestScene.AddObject(Obj);Ray r;cout<<TestScene.Objects[0]->GetGeom().GetDistanceUntilHit(r)<<endl;system("PAUSE");return EXIT_SUCCESS;}


i think it has something to do with this line:
TestScene.Objects[0]->GetGeom().GetDistanceUntilHit(r)

But i can't see why.(i am not a very good programmer)

Thank you


Your problem is that you're attempting to deference the Geometry pointer of the object you created, and that pointer doesn't point to anything. You have to make sure that the object had a valid Geometry object, either by creating one in the Object constructor or by assigning it elsewhere.
Quote:Original post by Martijnvdc
Thank you both very much, you really helped me!
I do use header files and such but i made it in one file so you could read it easier, and i removed a lot of parts that didn't matter.
Now it compiles and links but it crashes.


*** Source Snippet Removed ***

i think it has something to do with this line:
TestScene.Objects[0]->GetGeom().GetDistanceUntilHit(r)

But i can't see why.(i am not a very good programmer)

Thank you


In your constructor, you have neglected to set the 'Geom' member pointer. You presumably wanted it to copy the passed-in pointer value.

However, this is symptomatic of a more general problem: you're trying to use pointers for everything, without apparent justification. All the 'new' objects will get leaked, and become increasingly difficult to keep track of.

One good reason for storing a Geometry instance by pointer within the Object is so that you can get polymorphic behaviour (by calling a member function through a base pointer) out of an object (the Object instance, which becomes a wrapper for that pointer). However, (a) you lose this value completely by storing pointers-to-Object in a vector, and (b) to make it work properly takes a bit of work, in order to make sure that Object instances can be copied properly.

Please pay very careful attention. There are a lot of subtle but very necessary things in here.

#include <iostream>#include <vector>#include <algorithm> // I'll be using this later.// In C++, classes and structs are interchangeable; all that differs is// the default of public vs. private access. So if you're going to make// all your members (all 1 of them) public anyway, just use struct.struct Ray { float test; };// If you're putting everything in one file anyway, there's nothing to gain// by pulling a member function's declaration out of the class body. Just define// it inside. But really, we shouldn't try to make a "default" implementation// of "geometry" in this case. Creating an instance of the base class is// probably an error, so let's define things in a way that will let the// compiler catch that for us:struct Geometry {  virtual float GetDistanceUntilHit(Ray &r) = 0;  // The '= 0' denotes a "pure" virtual function. A class which contains these  // And we'll need two more functions...  // First off, any time you have virtual member functions, you should have  // a virtual destructor, too. That's because destructors are subject to the  // same rules as normal functions when it comes to dispatch. If you call  // 'delete' on a base class pointer, you'll want to make sure the derived  // version of the destructor gets called.  virtual ~Geometry() = 0;  // Don't worry about this one yet. I'll explain later.  virtual Geometry* clone() = 0;};// Like that.class Object {  Geometry* g;         public:  // This is how we will set the pointer:  Object(Geometry* g = 0): g(g) {} // i.e., via an initialization list.  // Notice how a default argument is used, to make sure that we still  // have a default constructor, and so that the 'g' pointer gets initialized  // in that case, too.  // Instead of asking the object for its geometry and talking to it directly,  // OO design dictates that we ask the object to talk to the geometry on our  // behalf. Thus:  float GetDistanceUntilHit(Ray& r) { return g->GetDistanceUntilHit(r); }  // Notice how this function isn't virtual itself, but it will dispatch to  // one that is, so we still can get polymorphic behaviour.  // Now, the hard part.  // Our Object "owns" the Geometry that was passed to it, so it must clean it  // up when it dies:  ~Object() { delete g; }  // If we make a copy of the Object (and the std::vector will do so), we need  // to make sure that the copy gets a copy of the underlying Geometry. We can't  // just trust the compiler here, because it will mindlessly copy the *pointer*  // to geometry instead. And that's bad because it means the two Objects are  // suddenly sharing their Geometry. If one changes the Geometry, the other  // gets affected (sometimes desirable, but not here); and if both get  // destroyed, there will be two separate attempts to delete the pointed-at  // Geometry (absolutely, definitely never desirable; undefined behaviour).  // Therefore, we define a "copy constructor" which uses that 'clone' function  // to make a copy of the underlying geometry:  Object(const Object& o): g(o.g->clone()) {}  // And finally, there's one other way that an Object could be effectively  // "copied": by assignment to an existing Object. Thus we have to implement  // an "assignment operator" as well, which dictates what happens when the  // current Object is assigned to from another Object. There are a few ways to  // handle this, but the following is safe and general-purpose. Our strategy  // is to make a copy using the copy constructor, and then swap pointers  // between ourself and the copy. Then, the copy will automatically get its  // destructor called (because it's a local variable), taking our old Geometry  // with it (because of the pointer swap).  Object& operator=(const Object& o) {    Object copy(o);    std::swap(g, copy.g); // std::swap lives in <algorithm>.    return *this;  }};// Now we can implement the Geometry interface...struct Sphere : Geometry {  float Radius;   // This technique should look familiar now:  Sphere(float Radius = 10.0f): Radius(Radius) {}  // in C++, "once virtual, always virtual"; we don't need to say again  // that this is a virtual function.  float GetDistanceUntilHit(Ray &r) { return 2.0f; }  // Each derived instance will have to implement clone() similarly to this:  Geometry* clone() { return new Sphere(*this); }  // Of course, if the derived class is complicated enough, it might need  // its own copy constructor, which gets used here ;)  // There is no need to fill in the destructor, in this case.};class Scene {  // Don't bother defining constructors that will be empty.  // Anyway, now we can store objects by value:  std::vector<Object> Objects;  // And add them by value:  public:  void AddObject(Object& Obj) { Objects.push_back(Obj); }  // And again, let's not have outside code reach into our guts:  float DistanceUntilHit(Ray& r, int index) {    return Objects[index].GetDistanceUntilHit(r);  }};int main() {  // Why not put the test scene in here? :)  Scene TestScene;  // We don't need any more variable declarations in order to add an object:  TestScene.AddObject(Object(new Sphere()));  // Similarly here:  std::cout << TestScene.GetDistanceUntilHit(Ray(), 0) << std::endl;  // Don't pause your programs artificially at the end.}
Incredible how much effort you put in helping me :)
I really appreciate it :p

I changed TestScene.GetDistanceUntilHit(Ray(), 0) to TestScene.DistanceUntilHit(Ray(), 0).
I think i understand what you typed, so i tried to compile and run it.
But i get some errors:

C:\PROJECTS\Tests\Test2\main.cpp In function `int main()':
116 C:\PROJECTS\Tests\Test2\main.cpp no matching function for call to `Scene::AddObject(Object)'
note C:\PROJECTS\Tests\Test2\main.cpp:105 candidates are: void Scene::AddObject(Object&)
118 C:\PROJECTS\Tests\Test2\main.cpp no matching function for call to `Scene::DistanceUntilHit(Ray, int)'
note C:\PROJECTS\Tests\Test2\main.cpp:107 candidates are: float Scene::DistanceUntilHit(Ray&, int)
C:\PROJECTS\Tests\Test2\Makefile.win [Build Error] n\make.exe: *** [main.o] Error 1

I don't get these errors if i use this instead:
  Object TestObj(new Sphere());  TestScene.AddObject(TestObj);  Ray r;  std::cout << TestScene.DistanceUntilHit(r, 0) << std::endl;

I get diffirent errors then:

C:\PROJECTS\Tests\Test2\main.o(.text$_ZN6SphereD1Ev[Sphere::~Sphere()]+0x13) In function `ZN6SphereD1Ev':
[Linker error] undefined reference to `Geometry::~Geometry()'
[Linker error] undefined reference to `Geometry::~Geometry()'
C:\PROJECTS\Tests\Test2\main.o(.text$_ZN6SphereD1Ev[Sphere::~Sphere()]+0x13) ld returned 1 exit status
C:\PROJECTS\Tests\Test2\Makefile.win [Build Error] n\make.exe: *** [Test1.exe] Error 1

Thank you for helping me :p
Hey,
I managed to solve those errors now :p

Now i can finally continue making my small-simple-raytracer-remake.
My previous one was extremely bad structured and too slow for what i want it to be.

http://img99.imageshack.us/my.php?image=raytracer1cc4.jpg

That image was calculated in 434ms(on average) on 1 core of a q6600 2.4Ghz.(with old raytracer)

btw, this forum ROCKS: You people are very kind and helpfull, just look at what Zahlman typed for me! :-)

This topic is closed to new replies.

Advertisement