views:

529

answers:

5

Suppose we have a class A and class B and C inherit from it. Then we create an array of references to A's, and fill it with B's and C's. Now we decided that we want to eliminate all the C's. Is there a way to check what type each field of the array really holds without doing something redundant like a returnType() function?

Edit: fixed "array of A's" to "array of references to A's".

+12  A: 

You can't create an array of As and fill it with Bs and Cs - they will be sliced down to
As. You can create a n array of pointers to A , wich you can populate with pointers to B and pointers to C.

To check the type of something in this situation - use dynamic cast:

    // create B or C randomly
    A * a = rand() % 2 ? new B : new C;

    if ( dynamic_cast <B *> ( a ) ) {
        // it's a B 
    }
    else {
        // it isn't (must be C in this case)
    }
anon
This is what I meant... Pointers to A's. Fixed question.
Epsilon Vector
Sorry, but that's just not true, I often use a vector/array of As to hold Bs and Cs, you just have to do weird stuff with the address operator for A and its merry children. It's a lot of work.
Ed Woodcock
However, the rest of the answer is perfectly valid (wasn't enough space to talk about that). +1
Ed Woodcock
@Ed - post some code that explains how you do that on an answer.
anon
@Epsilon - you can't have an array of references, either - it's got to be pointers.
anon
It's pretty complex, but you can do it by using an address() function, an offset() function and a custom allocator. You basically tell the allocator to reallocate space based on the size provided by the address() operator and use offset() to make it the right size based on the class.
Ed Woodcock
@ed sorry no show, no believe. If a B is bigger than am A you cannot have an array of them - full stop. You can have arrays of surrogate objects of some sort, but that is not the same thing.
anon
@Neil - Why won't references work? Aren't they essentially pointers with a more convenient functionality (no need to cast)?
Epsilon Vector
@Neil I'll see if I can distill the code down a little (It's part of my project, and so very very complex). Although, however it's vectors not arrays, that might cause a difference, my bad.
Ed Woodcock
@Ed: Neil's right in the general case -- if B and C are derived from A, they are almost certainly larger than it, and will be sliced (google "object slicing"). This (unfortunately) is not flagged as an error in C++.
j_random_hacker
No, references are not pointers. And they won't work because they must always refer to something (unlike pointers) and when the array is created, they can't refer to anything as you have no Bs and Cs as yet.
anon
@null Yes, but you can override this functionality if you edit the allocators correctly, forcing an address offset that ensures each container is the correct size. I'm working on some code example.
Ed Woodcock
Oh so you're all seeing my name as "null" too! It's actually "j_random_hacker". (Though "null" is not such a bad tagname... :) )
j_random_hacker
Yeah, if you go onto your profile the correct name is in the page title but your name field is empty. I think you broke it =D
Ed Woodcock
@Ed: Even if you mess with the offsets somehow, I can't see how you can get around the fact that vector<A> will call **A's copy constructor** whenever an A, B or C object is assigned to one of its elements. But I'll wait for your code before saying it's impossible...
j_random_hacker
Ok, I made a mistake here: The actual array that's being allocated etc. is of pointers, yes. However, each pointer 'points' to a class A, which is actually a class B or C, and the offset is used to increment the pointers from the original array. My apologies, guess I had the wrong idea!
Ed Woodcock
@Ed: Thanks, yeah, it must have been that cord I tripped over earlier marked "j_random_hacker's name, don't unplug" :-P
j_random_hacker
If you'd like I can provide code to show what I meant though, I don't want you to think I was just making something up to sound clever, that would be retarded! NB all the A/B/Cs are in an array so the idea still stands, it's just that pointers are used in the naked array.
Ed Woodcock
@Ed: No sweat. :) Yes, it's fine to treat pointers-to-A this way (otherwise polymorphic containers would be impossible!)
j_random_hacker
@still_j yeah, I just got a little confused. It's amazing how easily you fry your brain after spending 50 hours a week on the same code for the past 7 months. Sorry guys!
Ed Woodcock
Ed: if your vector/array is of pointer's to A, you shouldn't need any custom allocators and offset stuff. dynamic_cast does this all for you.std::vector<A *> arr; arr.push_back(new B); B *p = dynamic_cast<B *>(arr[0]); if(p) { /* element 0 is actually a B */ }
Evan Teran
@Evan: it sounds like he has two parallel arrays, one that's full of A pointers, and one that full of variable-sized Bs, and Cs
Eclipse
@Evan Josh is correct, the A/B/Cs are all concurrent in memory, and so you need the offsets in order to iterate over them correctly. Like I said originally, it's pretty complex.
Ed Woodcock
I think it worth adding that depending on the hierarchy structure and how many times this call is used, 'dynamic_cast' is potentially slower than using say a virtual function: "virtual bool isC();"
Richard Corden
A: 

You won't be able to store B's in it. The compiler will moan.

Dimitri C.
Unfortunately, the compiler will not moan -- it will just "slice" the B objects down to A objects. (Google "c++ object slicing".)
j_random_hacker
No no: the compiler will moan, because B is not derived from A (see the original question).
Dimitri C.
A: 

This will only work if the array of A's is actually an array of pointers to A, otherwise you'll get slicing problems.

As for the problem at hand, you're better off giving A a virtual function getClassName, and ovverriding it appropriately in subclasses, then using the result to provide the behaviour you want.

Visage
+3  A: 

Ideally, you want to figure out what functionality it is that makes you not want the Cs in there. Then add a virtual function that exposes that property. This way when you add class D that also has that same unwanted property, things will continue to behave correctly.

class A
{
public:
    virtual bool IsFrobbable() { return true; }
};

class B : public A
{
};

class C : public A
{
public:
    virtual bool IsFrobbable() { return false; }
};

int main()
{
     vector<A *> vA;
     vA.push_back(new A());
     vA.push_back(new B());
     vA.push_back(new C());

     vA.erase(remove_if(vA.begin(), vA.end(), not1(mem_fun(&A::IsFrobbable))));
     // Now go ahead and frob everything that's left
}
Eclipse
Thanks but in this case it's not something that can be expressed with an attribute (it's different behavioral rules).
Epsilon Vector
Without seeing the actual code, I'd highly doubt that there isn't some distinguishing property relating to the fact that you don't want Cs in the array. (Even if that property is just IsC()). It's going to be faster, clearer, and more maintainable as the heirarchy grows than trying a dynamic_cast.
Eclipse
Well what I'm doing is implementing a chess game. An 8x8 array of pointers to game pieces contains game pieces. Only rooks, the king, and pawns are distinguishable by the data fields, the rest of the difference is in the moving rules.
Epsilon Vector
Supplement: not that in a chess game you would need to suddenly kill off all pieces of a certain type, this is just a question that popped up in my head when I was coding.
Epsilon Vector
A: 

It's not possible to create an array of references in C++.

It is possible to create an array of pointers to A, however. In that case, provided A declares at least 1 virtual function, you could use C++'s run-time type inference (RTTI) mechanism to do what you want to do:

A* arr[10];

fill_with_as_bs_and_cs(arr);

for (int i = 0; i < 10; ++i) {
    if (dynamic_cast<C*>(arr[i])) {
        arr[i] = 0;   // You can't really "remove" things from an array...
    }
}

However, this is not the most maintainable approach -- any time you need to distinguish a C from an A, you'll need to use this if test, and if you wind up deriving more classes from A and you also need to treat them differently, you'll need to update every place in your code where you are performing this "type testing." As your codebase gets bigger, it quickly gets easy to miss places.

Generally, it's much more maintainable to use a virtual function that is overridden in each class as necessary. In this particular case, establish what are the general criteria shared by all A-derived class objects that should be removed from the array -- let's say that an object should be removed if it is blargable -- and then write a virtual function bool isBlargable(), and test that instead.

j_random_hacker