Thread: Why does push_back, etc, not return an iterator?

  1. #1
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708

    Why does push_back, etc, not return an iterator?

    Is there a good reason for this?
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  2. #2
    Code Goddess Prelude's Avatar
    Join Date
    Sep 2001
    Posts
    9,897
    >Is there a good reason for this?
    Because you already know where the item is and can acquire an iterator for it with ease. Why do you need it to return an iterator?
    My best code is written with the delete key.

  3. #3
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    >> Because you already know where the item is and can acquire an iterator for it with ease.

    Well sure, you can do:

    Code:
    iterator 
     push_back(T b)
    {
     base::push_back(b);
     iterator c = end();
     return --c;
    }
    But that's just fugly.

    >> Why do you need it to return an iterator?

    I'm storing the iterator for a future erase() operation.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  4. #4
    Code Goddess Prelude's Avatar
    Join Date
    Sep 2001
    Posts
    9,897
    >But that's just fugly.
    It works though. Better to use a fugly but working solution than to complain about the decisions of the standard library and not get a solution.

    >Well sure, you can do:
    Or better yet:
    Code:
    iterator push_back(T b)
    {
      base :: push_back(b);
      return --end();
    }
    Saves you a temporary.

    >I'm storing the iterator for a future erase() operation.
    Are you doing this with every item you push? A search operation to get the proper iterator may be the cleaner option if you don't save these iterators frequently.
    My best code is written with the delete key.

  5. #5
    &TH of undefined behavior Fordy's Avatar
    Join Date
    Aug 2001
    Posts
    5,793
    According to the standard, if push_back generates an exception (possible as the vector or other container may need to allocate more memory) there will be no effect....it either works or it doesnt....this is due to exception safety promises that allow clients of the containers to use them without worrying that the container code will throw (some members do indeed throw, but they are specifically documented).....if the code does generate an exception and you assign the returned object to something in your code, then you may well be assigning to something that is wrong or not there.

    Herb Sutter discusses this in his articles about "Exception Safe" containers

  6. #6
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Hmmm, ok, thanks Fordy, but...push_back() internally uses insert(), which...returns an iterator! Weird, right?

    >> Or better yet:

    O.K., that's a bit cleaner, thanks. (though I'm not sure it really saves on a temporary)

    >> Are you doing this with every item you push?

    No. depending on the 'mode' we're in, a 'purge' list of iterators (which can't be immediately erased due to other dependancies) is built.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  7. #7
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Originally posted by Sebastiani
    Hmmm, ok, thanks Fordy, but...push_back() internally uses insert(), which...returns an iterator! Weird, right?
    Not at all. Just because your STL implementation uses insert internally doesn't mean that any other does.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  8. #8
    Registered User jlou's Avatar
    Join Date
    Jul 2003
    Posts
    1,090
    By the way, its hard to tell based on your example solution, but if you are considering creating a derived container class that publicly inherits from one of the STL containers so that you can have this alternate push_back I would recommend considering a different solution. This is because, among other things, the STL containers were not designed to be publicly derived from.

  9. #9
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Originally posted by jlou
    By the way, its hard to tell based on your example solution, but if you are considering creating a derived container class that publicly inherits from one of the STL containers so that you can have this alternate push_back I would recommend considering a different solution. This is because, among other things, the STL containers were not designed to be publicly derived from.

    Why would you say that?
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  10. #10
    Registered User jlou's Avatar
    Join Date
    Jul 2003
    Posts
    1,090
    I'm not really an expert in this - I am merely regurgitating things I have read elsewhere - but basically, if a class doesn't have any virtual functions (and therefore has a non-virtual destructor), then it should not be inherited from.

    If you do that, then depending on how your code is used, it could lead to errors and/or undefined behavior. I believe the standard states that using delete on a pointer of a base type without a virtual destructor, whose actual dynamic type is a derived class, is undefined. For example:
    Code:
    #include <iostream>
    #include <vector>
    
    class BaseClass
    {
    public:
        BaseClass() { }
        // Non-virtual destructor
        ~BaseClass()  { std::cout << "In Base Destructor." << std::endl; }
    };
    
    class DerivedClass : public BaseClass
    {
    public:
        DerivedClass() { }
        // Virtual does not matter here.
        virtual ~DerivedClass() { std::cout << "In Derived Destructor." << std::endl; }
    };
    
    typedef std::vector<BaseClass*> BaseClassPtrArray;
    
    void EmptyAndDelete(BaseClassPtrArray& objList);
    
    int main()
    {
        BaseClassPtrArray myObjList;
    
        myObjList.push_back(new DerivedClass);
        myObjList.push_back(new DerivedClass);
    
        EmptyAndDelete(myObjList);
    }
    
    void EmptyAndDelete(BaseClassPtrArray& objList)
    {
        BaseClassPtrArray::iterator iterObjPtr = objList.begin();
        BaseClassPtrArray::iterator iterEnd = objList.end();
        for(; iterObjPtr != iterEnd; ++iterObjPtr)
        {
            BaseClass* pDeadObj = *iterObjPtr;
            delete pDeadObj; // Undefined behavior
        }
        objList.clear();
    }
    On my machine (MSVC++ 6.0 with upgraded Dinkumware libraries), the base class destructor is called and the derived class destructor is not in the default Release build. In the Debug build I get an error. Clearly, if there is anything important happening in the derived destructor, even in my Release configuration that doesn't crash, that code will not be executed.

    I have seen some that have argued that they don't do anything in their derived destructor and that they "know" that a base class pointer to the derived object will never be deleted. I personally don't think that makes it any smarter, but I guess that is for you to decide.

    If you only want to add functionality to the container (i.e. enhance it with new functions) then you can use containment: have a member variable of the base type and write a bunch of wrapper functions; or use global functions, possibly in a separate namespace, that take the container as template parameter. For example, something like this might work for you:
    Code:
    #include <iostream>
    #include <vector>
    #include <list>
    
    // This is your new push_back which works with any
    // push_back(), end(), and -- compatible container.
    template<class C, class T>
    C::iterator my_push_back(C& container, T object)
    {
        container.push_back(object);
        return --container.end();
    }
    
    // Example usage:
    #if 1
    typedef std::vector<int> TestContainer;
    #else
    typedef std::list<int> TestContainer;
    #endif
    
    int main()
    {
        TestContainer intArray;
        TestContainer::iterator iterPushed = my_push_back<TestContainer, int>(intArray, 1);
        if (iterPushed == intArray.begin())
            std::cout << "Success!" << std::endl;
        else
            std::cout << "Failure!" << std::endl;
    
        iterPushed = my_push_back<TestContainer, int>(intArray, 1);
        if (iterPushed == ++intArray.begin())
            std::cout << "Success!" << std::endl;
        else
            std::cout << "Failure!" << std::endl;
    }
    I used the typedefs in the example to show that you can try putting other containers in there without changing the push_back function. I'm sure there is an even better way to do that (I don't think the T template parameter is needed), but you get the idea. I believe this is the same idea used by algorithms in <algorithm>, among other things, which are nice because they work for any compatible container.

    Hope that helps clarify things and hopefully if I misstated anything somebody will correct me.

  11. #11
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    I've also used private inheritance from a container, and using declarations to expose some of the members. Since you may not cast an object pointer to a pointer to a private base, this is perfectly safe.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  12. #12
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    jlou: of all virtual functions, only the destructor is of any interest. If a class doesn't have a virtual destructor but other virtual functions, it has a grave design error. I believe GCC issues a warning on this.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  13. #13
    Registered User jlou's Avatar
    Join Date
    Jul 2003
    Posts
    1,090
    Thanks CornedBee.

    By the way, here is an updated version of the global push_back that doesn't require the second template parameter. It was just bugging me before so I went ahead and looked up the value_type typedef.
    Code:
    #include <iostream>
    #include <vector>
    #include <list>
    #include <string>
    
    template<class C>
    C::iterator my_push_back(C& container, const C::value_type& object)
    {
        container.push_back(object);
        return --container.end();
    }
    
    // Example usage:
    //typedef std::vector<char> TestContainer;
    //typedef std::list<char> TestContainer;
    typedef std::string TestContainer;
    
    int main()
    {
        TestContainer charContainer;
        TestContainer::iterator iterPushed = my_push_back<TestContainer>(charContainer, 'j');
        if (iterPushed == charContainer.begin())
            std::cout << "Success!" << std::endl;
        else
            std::cout << "Failure!" << std::endl;
    }

  14. #14
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Something just occurred to me. I believe this implementation would be more efficient:
    Code:
    template<class C>
    C::iterator my_push_back(C& container, const C::value_type& object)
    {
        return container.insert(container.end(), object);
    }
    Always be careful of storing iterators to vectors though, they are easily invalidated.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. New string functions
    By Elysia in forum C Programming
    Replies: 11
    Last Post: 03-28-2009, 05:03 AM
  2. OpenGL Window
    By Morgul in forum Game Programming
    Replies: 1
    Last Post: 05-15-2005, 12:34 PM
  3. Binary Search Trees Part III
    By Prelude in forum A Brief History of Cprogramming.com
    Replies: 16
    Last Post: 10-02-2004, 03:00 PM
  4. problem with open gl engine.
    By gell10 in forum Game Programming
    Replies: 1
    Last Post: 08-21-2003, 04:10 AM
  5. opengl program as win API menu item
    By SAMSAM in forum Game Programming
    Replies: 1
    Last Post: 03-03-2003, 07:48 PM