Friday, April 29, 2011

Do templated classes inherit the members of the classes passed to them? (Specificly std::vector)

Hi,

I have a question regarding vectors:

If I have a std::vector<MyClass> will this vector "inherit" the MyClass member functions or not? If not, then would be the best way to handle individually the MyClass members inside a loop? Would I have to create a new class object on each iteration and assign the current vector iterator to it?

class MyClass
{
    public:
        void setMembers(std::string &string, int &num)
        {
            mystring = string;
            number = num;
        }
        string printString() { return mystring; }
        int printNumber() { return number; }

    private:
        std::string mystring;
        int number;
};

    MyClass test;
    std::vector<MyClass> vector;

    std::string string = "Test 1";
    int a = 3;

    test.setMembers(string,a);
    vector.push_back(test);

    for(unsigned int i = 0; i<vector.size(); i++) {
        cout << "Vector #" <<  << endl;
        // does vector inherit member functions here?
        cout << "mystring is: " << vector.printString()<< endl;
        cout << "number is  : " << vector.printNumber() << endl;
    }

Thanks a lot in advance for your help.

From stackoverflow
  • No it does not. You need to access each member of the collection:

    cout << "Vector #" <<  << endl;
    for( unsigned int i = 0; i <vector.size(); i++) {
        cout << "mystring at " << i << " is "  << vector[i].printString()<< endl;
        cout << "number at " << i << " is " << vector[i].printNumber() << endl;
    }
    
  • No. Vector is a container around objects of your class type. You need to index your vector with operator[] or the at() member functions to access your class objects. Then you can call those functions on your objects.

    E.g.

    v[i].printString();

    or

    v.at(i).pringString();

  • No, the instance std::vector doesn't inherit your member variables. However the objects in the vector do have those member which you can access via operator[].

    for (size_t i = 0; i < vector.size(); i++) {
        cout << "Vector #" << i << endl;
        cout << "mystring is: " << vector[i].printString() << endl;
        cout << "number is  : " << vector[i].printNumber() << endl;
    }
    

    Notice we say vector[i] now, which returns a value of type MyClass which does have the member functions printString() and printNumber().

    You should go re-read the chapter on inheritance.

  • No. A vector is like an array; it's a collection of what it holds, not a subclass of what it holds.

  • No, the vector does not "inherit" members of the class. If you want to do something for each element of the vector, use an iterator:

    for(vector<MyClass>::iterator i=vector.begin();i!=vector.end();i++) {
        cout << "mystring is: " << i->printString() << endl;
        cout << "number is  : " << i->printNumber() << endl;
    }
    
  • The vector inherits nothing from its class, but the members are members of the class.

    Suppose you had std::vector<MyClass> v;, and actually got some members in (it looks like you're fine with .push_back()).

    Now, you can call the MyClass functions from something like:

    for (int i = 0; i < v.length(); ++v)
       v[i].printString();
    

    or

    for (std::vector<MyClass>::const_iterator i = v.begin(); i != v.end(); ++i)
       i->PrintString();
    
  • Consider it from the "Is a / has a" point of view The vector isn't a "MyClass" it has a "MyClass" (actually it has 0 or more MyClass), but the important thing is that is isn't a MyClass, it just stores them

    Each object you put into the vector stays there, you can refernce each obect by position (as if it were an array of MyClass objects)

    for(unsigned int i = 0; i<vector.size(); i++) {
        cout << "Vector #" <<  << endl;
        // does vector inherit member functions here?
        cout << "mystring is: " << vector[i].printString()<< endl;
        cout << "number is  : " << vector[i].printNumber() << endl;
    }
    

    however the convention is to use iterators, which act "like" pointers to the stored objects e.g.

    for(std::vector<MyClass>::iterator i = vector.begin(); i != vector.end(); ++i) {
        cout << "Vector #" <<  << endl;
        // does vector inherit member functions here?
        cout << "mystring is: " << i->printString()<< endl;
        cout << "number is  : " << i->printNumber() << endl;
    }
    

    Hope this helps to clarify things.

    Kevin : You need to fix your example to be "std::vector::iterator" type for i
    Binary Worrier : Thanks Kev, it's my own fault for typing code directly into an answer :)
  • Thanks for all the answers! It turned out to be easier than I thought and it makes a lot of sense now.

    I shouldn't have used the word inherited since it means a totally different thing.

    Anyways, all of your answers helped a lot. Thank you very much!

  • The first part of your question has already been answered by others. No form of inheritance takes place. The vector behaves as a vector, and nothing else.

    There are two ways to manipulate arrays. The first (obvious) one is through a for loop, like you said:

    for(size_t i = 0; i<vector.size(); i++) { // technically, you should use size_t here, since that is the type returned by vector.size()
        cout << "Element #" <<  << endl; // We're iterating through the elements contained in the vector, so printing "Vector #" doesn't make sense. There is only one vector
        cout << "mystring is: " << vector[i].printString()<< endl; // [i] to access the i'th element contained in the vector
        cout << "number is  : " << vector[i].printNumber() << endl;
    }
    

    The other approach is to use the algorithms defined in the standard library. As an introduction to those, I'm going to split it up into a few steps. First, every container also defines an iterator type. Iterators are conceptually like pointers that point to a location in the container. So instead of vector[i].printString(), you can call printString() on the element point to by any given iterator. (assuming an iterator called iter, the syntax would be iter->printString())

    The reason for this is that it allows a common and generic way to traverse containers. Because lists, vectors, deques and all other container types all provide iterators, and these iterators use the same syntax, your code can take a pair of iterators, denoting the beginning and the end of the range of elements you want to process, and then the same code will work regardless of the underlying container type.

    So first, let's use a loop to run through the container again, but this time using iterators:

    forstd::vector<MyClass> current = vector.begin(); current != vector.end(); ++current) {
        cout << "mystring is: " << current->printString() << endl;
        cout << "number is  : " << current->printNumber() << endl;
    }
    

    Not a huge improvement so far, although it does eliminate the i index variable, which often isn't necessary, except as a loop counter. The begin/end) functions return an iterator pointing to the first element in the container, and another pointing one past the end of the iterator. So as we move the first iterator forward, we know we've reached the end when it equals the end iterator. In this way, two iterators can represent any range of elements.

    Now that we have iterators though, we can use a lot of other tricks. The C++ standard library comes with a number of algorithms for processing sequences of elements. They're located in the <algorithm> header.

    A simple one to get us started is std::for_each, which is almost a drop-in replacement for a for loop. It is simply a function which takes two iterators, denoting the range of elements it should process, and an action it should perform on each. So to call it, we need to define such an action:

    void Print(const MyClass& obj) {
        cout << "mystring is: " << obj.printString() << endl;
        cout << "number is  : " << obj.printNumber() << endl;
    }
    

    That's all. A function which takes the element type as a parameter, and does whatever needs to be done. Now we can call for_each:

    std::for_each(vector.begin(), vector.end(), Print);
    

    If you need to do this often, it saves a lot of typing. The Print function only has to be defined once, and then every for loop can be replaced with such a one-liner.

    Another nice trick with iterators is that they don't have to represent the entire range. We could skip the first five elements:

    std::for_each(vector.begin() + 5, vector.end(), Print);
    

    or take only the first three elements:

    std::for_each(vector.begin(), vector.begin()+3, Print);
    

    or any other manipulation you can think of. There are also algorithms such as copy (copy from one iterator range to another):

    std::copy(vector.begin(), vector.end(), dest.begin());
    

    And dest may be any type of iterator as well, it doesn't have to be a vector iterator just because the source is. In fact we could even copy directly to std::cout if you wanted to print out the contents directly (unfortunately, since MyClass doesn't define the operator <<, that would result in an error.)

    to work around this little problem with std::cout, we could use std::transform, which applies some transformation to each object, and then places the result into an output sequence. Since we can't directly print out a MyClass objec, we could just transform it to a string, which can be printed out:

    std::string ToString(const MyClass& obj) {
      return std::string("mystring is: " + obj.printString() + "\nnumber is  :" << obj.printNumber() + "\n";
    }
    

    Again, fairly simple code. We simply create a function which takes a MyClass object, and builds a string with the desired output. So let's copy this directly to std::cout:

    std::transform(vector.begin(), vector.end(), std::ostream_iterator(std::cout), ToString);
    

    std::ostream_iterator creates a special output stream iterator out of std::cout to allow it to function as an iterator. And once again, the actual "do this on everything in the vector" code became a single line. The actual action to perform is defined once, elsewhere, so it doesn't have to clutter up the code.

    So while a for loop is the immediately obvious way to process sequences of elements in a container, iterators are often a better solution in the long run. They offer a lot more flexibility, and even simplifies your code quite a bit.

    I won't blame you if you prefer to stick with for loops for now, as they're a bit easier to grok. I simply wanted to show you that they're not the "ultimate" answer in C++.

    nmuntz : Thank you very much for your very thoroughly explanation, this will help me a lot actually! I really liked the for_each() solution that you gave. I really appreciate all the extra effort you took here to explain to me this. Thanks a lot!!!
    jalf : No problem. It's always a pleasure to help people discover that there's actually a fairly elegant language hidden inside C++. It's well hidden, but knowing it's there can save you a lot of headaches ;)
    Binary Worrier : Dude +1 for a truly EPIC answer!
  • No, std::vector<MyClass> does not inherit the members of MyClass but you can use STL's and boost's facilities to perfrom operations on the vector without explicitly coding an iteration for each operation you need.
    Here's some sample code:

    #include <vector>
    #include <algorithm>
    #include <boost/bind.hpp>
    
    struct Bla
    {
        Bla(int i = 0) : m_i(i) {}
    
        void print() { printf("%d ", m_i); }
        void printAdd(int a) { printf("%d ", m_i +  a); }
    
        Bla add(int a) { return Bla(m_i + a); }
        int geti() { return m_i; }
    
        int m_i;
    };
    
    void printInt(int i)
    {
        printf("%d ", i);
    }
    
    int main(int argc, char *argv[])
    {
        std::vector<Bla> bla;
        bla.push_back(Bla(1));
        bla.push_back(Bla(2));
    
        // print the elements in the vector
        std::for_each(bla.begin(), bla.end(), boost::mem_fn(&Bla::print));
        printf("\n");
        // a complex operation on the vector requiring an additional argument for the call
        std::for_each(bla.begin(), bla.end(), boost::bind(&Bla::printAdd, _1, 10));
        printf("\n");
    
        // extract a single member from the vector into a second vector
        std::vector<int> result;
        result.resize(bla.size());
        std::transform(bla.begin(), bla.end(), result.begin(), boost::bind(&Bla::geti, _1));
        // print the result
        std::for_each(result.begin(), result.end(), &printInt);
        printf("\n");
    
        // transform the vector into a different vector using a complex function that requires an argument.
        std::vector<Bla> result2;
        result2.resize(bla.size());
        std::transform(bla.begin(), bla.end(), result2.begin(), boost::bind(&Bla::add, _1, 10));
        std::for_each(result2.begin(), result2.end(), boost::mem_fn(&Bla::print));
        printf("\n");
    
        return 0;
    }
    

0 comments:

Post a Comment