views:

194

answers:

5

Hi guys

I've been programming a while now at school, and I'm working on my first independent large project. I've been discovering a lot of things about programming that I haven't known before and it's been great.

However, more and more, I feel like I no longer understand C++ as a language the more I delve into it. I'd like to get some of my (mis) conceptions about references and pointers straight before continuing, and I hope that you, stackoverflow, could correct me if I'm wrong.

Thanks in advance, and here we go!

1. When converting between classes, what actually gets converted is the virtual table.

Example:

class A{
public:
    A() : x(0) {};
    int x;
    virtual void doStuff() {
        cout << x <<endl;
    }
};

class B : public A{
public:
    B() : y(1) {};
    int y;
    virtual void doStuff() {
        cout << y <<endl;
    }
};

If I had converted an object b of type B to A, what would happen internally is the virtual table of b would be discarded, and replaced with a corresponding virtual table of type A, and the destructor of y would be called because there is no longer a reference to it. Similarly, the doStuff in b would be made to point to the function address of A::doStuff instead of B::doStuff. The address pointing to x would stay the same however.

2. Which implies that the only way to take advantage of polymorphism is through pointers and references

As a consequence of point 1, the only way to take advantage of polymorphism in virtual methods of classes would be to use references and pointers, because if we passed by value, the classes itself would be automatically converted into the base class.

Example:

void doALotOfStuff(A a1, A a2) {
    a1.doStuff();
    a2.doStuff();
}

int main(){
    A a;
    B b;
    doALotOfStuff(a,b);
    return 0;
}

would print out

0
0  

because the compiler would generate code to convert b into A.

3. Furthermore, the only way to take advantage of this kind of polymorphism with arrays and STL containers would be to use pointers because references cannot be stored

Since vector would not work because references are not assignable, it follows that if I wanted to have a vector of base classes, I would need to create a vector of pointers of A in order to preserve the virtual table of elements of Type B.

Sorry if this was TL;DR, but it's be kind of bothering me for a while when I was doing some class design on my project, and I realized I couldn't really get away from just using either pointers or references because of library interfaces and polymorphism issues.

Thanks for any clarification you can provide!

A: 

What you are saying is correct, it is necessary to have pointers to take any advantage of polymorphism. It is all bound up in the virtual function tables as you describe.

In point 1 you talk about 'converting' from type B to A. This is a little confused as it is more accurate to say that a new object would be created with the appropriate components. See the next point about slicing.

In point 2 what you are demonstrating is the principle of slicing. This is where a derived class is has the derived part removed and only the base remains.

A point to note is that you should use either boost::shared_ptr or boost::unique_ptr in your STL container because otherwise memory management becomes a dangerous headache.

If you haven't heard of boost you can check it out here it is an extremely valuable resource.

radman
Slicing doesn't remove anything. The derived object stays completely intact. The copy only includes the base class members, but this is lack of copying the derived members, not removing them. You can't remove what never existed in the first place, and the copy never had the derived type.
Ben Voigt
+3  A: 

While you have some nice ideas in point #1, that's not actually how it works. Conversion is not done in-place, it is done by creating a new object which copies the members it knows about from the conversion source (which is to say, the base class members, unless you have some really weird forward declarations in your base class). The instance of the derived object is not changed in any way. Of course the copy has a different memory address from the original object. Passing by value ALWAYS involves copying.

Ben Voigt
Ahh okay, thanks for clearing that one up.
Xzhsh
+2  A: 
  1. When you say "convert an object b of type B to A", you may mean two different things:

    • create a new object of type A that is copied from b, or
    • make a pointer of type A* or a reference of type A& that points or refers to b.

    In either case, the original object b is unchanged. You may throw it away after the conversion, but that's a different issue, and the conversion alone doesn't alter b.

    (Except in rare cases when the conversion operator or the constructor involved takes a non-const reference to B and alters the argument. However, even in this case, it's not the vtable replacement as you mentioned.)

    Once an object is created, its type doesn't change throughout its lifetime (until it is destroyed). That means, the vtable replacement you mentioned never occurs.

  2. Yes, polymorphism in C++ is achieved through pointers or references.

    Note that, in your example, a1 and a2 are copied from a and b, and the conversion did not alter a or b in any way.

  3. Yes, you're right. Or any kind of smart pointer is acceptable (or preferable).

A: 

You're mixing up pass-by-value and pass-by-reference.

When you do pass-by-value, as in your example, you are copying the data from the old instance, and creating a new instance. The old instance does not get destroyed. The function will operate on the new instance, and will not operate on the instance you passed in.

When you do pass-by-value, and pass a derived type, object slicing can occur: http://en.wikipedia.org/wiki/Object_slicing

When you do pass-by-reference, no new instance is created, and the function operates on your existing instance. The type of reference doesn't really matter, other than the fact that it restricts what the function can do with your object. It is still operating on your object, and calling your derived implementation.

For argument's sake, passing a pointer is a hybrid between pass-by-value (you copy the pointer), and pass-by-reference (you don't copy the object the pointer references).

Because of all this, the only way to do polymorphism (act on a base interface, but with a derived implementation) is via pointers or references.

And yes, STL containers cannot work with references.

You can use smart pointers from Boost to get around some of the limitations of references, and to get around some of the aggravation of "dumb" pointers :)

Merlyn Morgan-Graham
Actually, the v-table gets replaced (conceptually at least, and in many cases actually) after the base constructor runs and before and user code in the derived class constructor runs. At this moment, the type of the instance actually changed. It happens again in reverse during destruction.
Ben Voigt
@Ben: You're right. I guess just not in his scenario. Will fix it
Merlyn Morgan-Graham
A: 

Your actual confusion comes with statement 1. Objects are never converted in C++ -- an object of a type is always an object of that type (except for some minor behind-the-scenes strangeness when constructing an object's base class which we won't talk about). Instead VALUES get converted, and when you convert a VALUE from one object type to another, what is really happening is that you're constructing a NEW object with data from the old object.

So in your example for 2, when you call doALotOfStuff, the compiler will call the constructor A::A(const B &) to create a new A to pass as the second argument a2. It will ALSO call the constructor A::A(const A &) to create a new A for a1, so a1 and a2 are completely different objects than a and b.

Chris Dodd
Ben Voigt
@Ben: true, but irrelevant -- constructors for A produce a new A (and generally don't modify their argument) regardless of which one actually gets called.
Chris Dodd