2

I'm trying to make a class inherits from other and override some methods. Classes 'header' is:

class Objeto {  
public:  
    virtual bool interseca(const Rayo &rayo, float magnitud);  
    virtual bool breakNormal(const Punto &punto);  
    virtual Vector normal(const Punto &punto);  

    int idMaterial;  
};

class Esfera: public Objeto {
public:
    int idMaterial;

    virtual bool interseca(const Rayo &rayo, float magnitud);
    // etc
};

Next in other place of the program (outside of Objeto and Esfera) I do:

// ObjectList is a Vector<Objeto>
Objeto o = esfera; /* Where esfera is a valid Esfera object */
ObjectList[0] = o;
ObjectList[0].interseca(rayo, magnitud);

What I want is to call the new version of interseca that is in Esfera. In this way I can add more objects (Cube, Triangle, etc) and treat them as generic "Objetos".

But instead of the Esfera implementation of interseca the program calls Objeto::interseca.

What is the correct way to do this override with C++? Is that the way to do overriding and I'm missing something or I'm plain wrong? Any tip or alternative to do that?

estebarb
  • 467
  • 5
  • 12
  • 1
    That mix of English and your native language is _very_ confusing for an international audience. Consider using only English identifiers. – sbi Oct 31 '10 at 21:11
  • 2
    @sbi: Whether they're named point, ray, vector, object, or foo, bar, baz, beefcake, I really don't care. The problem and solution is the same either way. – Merlyn Morgan-Graham Oct 31 '10 at 21:21
  • @Merlyn: but it's a lot easier to read the code when a) it is written in one language instead of two, and b) when you understand that language. As @sbi said, when asking an international audience for help with your code, it is worth considering just writing it all in english. And of course, I doubt you really mean that. I don't think I've ever met a programmer who "really don't care" about naming, which seems to be what you imply. If a vector class being named `beefcake` doesn't hinder readability for you, I can honestly say you've got some unique skills. ;) – jalf Oct 31 '10 at 22:56
  • Yes, sorry. Right now I'm thinking that for non spanish speakers it will sound terrible... I will consider that. Thanks for remember it. – estebarb Nov 01 '10 at 01:17
  • @jalf: On review, I think I see where you, and probably sbi, are coming from. For a *program* I care, and all identifiers should be English (for the when-in-Rome coding style best practice). For this question, I don't, because the types of the classes don't change how clear the question is. If the question were actually about linear algebra, it would matter. – Merlyn Morgan-Graham Nov 01 '10 at 04:47

4 Answers4

7

You can only get polymorphic behavior if you access the class via a pointer or reference. In addition to that, you are slicing the object when you are copying the derived type to a non-pointer/non-reference base type.

Slicing:

http://en.wikipedia.org/wiki/Object_slicing

Given these definitions:

#include<iostream>

class Objeto {  
public:  
    virtual bool interseca() {
        return false;
    }
};

class Esfera: public Objeto {
public:
    int idMaterial;

    virtual bool interseca() {
        return true;
    }
};

This won't do what you want:

Esfera e;
Objeto o = e;
std::cout << o.interseca() << "\n";

false

But this will:

Esfera e;
Objeto& o = e;
std::cout << o.interseca() << "\n";

true

Program design

A technique you can use to avoid this in the future is to make your base classes (pure) abstract.

Would you ever instantiate a true Objeto in your scene, or are you simply defining a base type? If you are just defining a base type (which I recommend), then you can make interseca, breakNormal, and normal pure virtual. Then, the compiler will catch problems like the one you have here.

class Objeto {  
public:  
    virtual bool interseca() = 0;
};

class Esfera: public Objeto {
public:
    int idMaterial;

    virtual bool interseca()
    {
        return true;
    }
};

// ...

Then, this will be okay:

Esfera e;
Objeto& o = e;
std::cout << o.interseca() << "\n";

compilation succeded

But this will cause the compiler to error - a good thing, cause it's catching a bug:

Esfera e;
Objeto o = e;
std::cout << o.interseca() << "\n";

error: cannot allocate an object of abstract type 'Objeto'

Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
  • Thanks! Just one thing to add. The field `int Identifier` can't be made virtual. To solve that just add a method getIdentifier and setIdentifier in the base class... Maybe there is another way to solve this detail but it worked for me. – estebarb Nov 01 '10 at 00:59
  • @estebarb: If you want a getter function signature to be defined in a base class, but want the actual value to be dependent on the derived class (and calculated by the derived instance), then you came up with the right solution. If you are calculating the id in higher level code, or it isn't calculated by a specific instance, then you don't need to make it virtual. It is a good idea to limit public access to a get/set function, though, even if you don't make those functions virtual. Define them in the base class, either way. – Merlyn Morgan-Graham Nov 01 '10 at 05:01
3

Your overriding is correct, but it seems you haven't fully grasped C++' object model yet. This

Objeto o = esfera;

doesn't do what you think it does. This creates a copy of the Objeto sub-object of esfera into o. (This is called "slicing".) What you want instead is to reference esfera using either a reference

Objeto& o = esfera; // note the & 

or o pointer

Objeto* o = &esfera;

If in doubt, always prefer references.

Community
  • 1
  • 1
sbi
  • 219,715
  • 46
  • 258
  • 445
2

You have overridden the method successfully. The problem is in the way you store objects and in the way polymorphic behaviour is achieved in C++.

To get polymorphic behaviour, you have to reference an object with a pointer or with a reference. C++ doesn't support reference type semantics in the same way as Java or C#. If you have a variable created like this

Objeto o = esfera;

variable o is a new object of the (static and dynamic) type Objeto created based on the object esfera. What happens here is called object slicing.

To get polymorphic behaviour you have to use a pointer or a reference. For example let's assume variables objeto and esfera are of the types Objeto and Esfera respectively.

Objeto* op = &objeto;
op->interseca(rayo, magnitud); // virtual dispatch is used but since op points
                               // to an object of type Objeto Objeto::interseca
                               // gets called.
op = &esfera;
op->interseca(rayo, magnitud); // virtual dispatch is used and this time
                               // Esfera::interseca gets called.

You can't store objects of different types in one vector. If you create a vector<Objeto> and try to store an object of type Esfera in it object slicing will happen just like in the example with variable o.

To have a container, that allows you to get polymorphic behaviour, you have to create a vector of some kind of pointers. That means you will have to solve a problem of the ownership of the objects, vector elements will reference. The best solution would most probably be to use a vector of some smart pointers.

Maciej Hehl
  • 7,895
  • 1
  • 22
  • 23
  • C++ *does* support reference type semantics like Java and C# - moreover, if you use reference semantics in the same way, you do get the expected polymorphic behaviour. – Arafangion Oct 31 '10 at 21:53
  • @Arafangion Oh really? So enlighten me please, why OP got object slicing with the code that would work in C#? – Maciej Hehl Oct 31 '10 at 22:02
  • The OP specifically said in his code that he wanted an object of type Objeto. He initialized that object with an Esfera object, so some kind of conversion took place. If he said instead that he merely wanted to *refer* to the Esfera object, then everything would've worked as he expected. – Arafangion Oct 31 '10 at 23:21
  • 1
    @Arafangion Yeah if he said he wanted to refer. The point is, that he had to say it explicitly. Here is the difference in the way you and I understand language support for reference semantics. Fine C++ does support it, but you have to write wrappers for pointers yourself and overload operator= for them. `int* ip = &i;` is not a reference semantics. It's a value semantics for a pointer. Anyway, I changed my answer to say, that the support is not in the same way. I hope you agree with that. – Maciej Hehl Oct 31 '10 at 23:29
  • Correct those are value semantics for pointers. Use reference semantics instead, ie "int& ip = i;", and there is no need to overload operator= for them, either. I still don't like the new reworded answer either, but it's better than before and I've no reason to downvote it anymore. The reason I don't like it is because C++ does support reference semantics very well - it is, however a fundamentally different language to C# and Java - using reference semantics if you did not specify that you wanted that semantic would be surprising. Java does not have value semantics except for primitive types – Arafangion Nov 01 '10 at 00:43
  • @Arafangion Initialization of a reference has a reference semantics, but initialization only. After the initialization, references have value semantics. Or am I wrong? – Maciej Hehl Nov 01 '10 at 00:51
  • @Maciej: Once a reference, it continues to be a reference, unless you pass the object to a function that wants value semantics, in which case value semantics will be used. If the function requires a pointer, then you'll have to dereference the reference, as per usual. – Arafangion Nov 01 '10 at 03:17
  • @Arafangion It stays being a reference, but it doesn't behave like one. It behaves like a variable and has value semantics. You can't store it in an array or any container, so this "reference semantics support" if you really want to call it this way, has no value for OP. The way variables behave when passed to a function depend on the way function takes arguments. If it takes a reference the reference gets initialized and as I admitted already we have a reference semantics here, but it doesn't depend on the nature of the variable passed. I can pass normal variable, not a C++ reference – Maciej Hehl Nov 01 '10 at 12:47
  • to such function and I'll get reference semantics when passing an object that normally has value semantics. To get fully blown reference semantics in C++ you not only have to wrap pointers and overload operator= but you have to solve one fundamental problem in the first place - memory management. Languages with gc solve that for you. In C++ you have to implement some clever reference counting or something similar to get this. Yes you can. If you want to call it a "language support for reference semantics" be my guest. – Maciej Hehl Nov 01 '10 at 12:50
  • @Maciej: Perhaps we can agree that if you don't specify the semantics you want, it will default to value semantics - however I still believe that the language supports reference semantics. It is when passing the value that the semantics matter, and it is at that function interface where the semantics are specified. If the function requires value semantics, then the object will be passed by value. If by reference, then by reference. If by pointer, then the user must pass a pointer by value - it is the callee that specifies the semantics needed, not the caller. – Arafangion Nov 02 '10 at 03:54
0

You created an Objeto on the stack. This item always has the type Objeto. For polymorphism you must use a reference or pointer.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 4
    The allocation of the object has nothing to do with it. Using a reference or a pointer does not require the object be allocated on the heap. – Merlyn Morgan-Graham Oct 31 '10 at 21:08
  • yea, but I think it's valuable to note the confusion. Objeto o = esfera is creation of an Objeto on the stack, instead of what the asker intended to do. That's what I think DeadMG means. – gtrak Oct 31 '10 at 21:33