STL containers as base classes

I know one is not supposed to derive from STL containers
because they have non-virtual destructors.

but that is boring.

I've noticed that boost::shared_ptr will destruct properly
on my system where delete will not.

but that doesn't stop use of bare pointers of course.

so what if you make operator new and operator& private so that
pointers cannot be used?

I've been playing with a wrapper like so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#ifndef BLOB_HPP
#define BLOB_HPP

// (c) bigearsbilly

namespace useful {

template<class T>
class blob : public T {

    private:
    void * operator new(size_t n){return 0; };
    void   operator& () { };

    public:
    class empty {};
    typedef typename T::value_type typ;
    typename T::iterator it;

    void unshift(typ s) { insert(T::begin(), s); };
    void push(typ s) { insert(T::end(), s); };

    typ pop() { if(T::empty()) throw empty();
                it = T::end();
                --it;
                typ s = *it;
                erase(it);
                return s;
                };

    typ shift() { if(T::empty()) throw empty();
                    it = T::begin();
                    typ s = *it;
                    erase(it);
                    return s;
                    };
};
}
#endif  


so I can mess about with different containers with the same interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef useful::blob< set<string> > blob; // change typedef to suit
int main(int argc, char ** argv) {

    string s;
    blob v;
    //blob * p = &v;
    //blob = new B;

    while(cin >> s) v.push(s);

    try {
        for(;;) cout << v.pop() << endl;
    } catch (blob::empty) { }
}


Is there anything dodgy about such an implementation?



Last edited on
Does it really solve the problem?

I could just derive D from blob and then I can

1) Instantiate D via new;
2) Upcast the pointer to a T*;
3) delete via pointer to T

and now D::~D() will not run.



hmm, I tried,
doesn't seem to like it.
I derived a class D. Can't even make a D pointer.

1
2
3
class D : public blob {

}
;

1
2
3
4
    D d; // ok
    D * pp = new D; // won't compile
    cout << &d;   // or this

Last edited on
1
2
3
4
5
6
7
8
9
10
11
class D : public useful::blob< std::vector<int> > {
  public:
    void* operator new( size_t sz ) { return ::operator new( sz ); }
    void* operator&() { return reinterpret_cast<void*>( this ); }
};

int main() {
    D d;
    D* pd = new D;
    std::cout << &d;
}


compiles for me.
yes OK
but you cheated and overloaded the operators.
;-)

I'm only after catching silly mistakes,
not to prevent the act of consciously breaking it.

it'll do for now then







Last edited on
How about:

std::set<string> & myRef = v;
std::cout << &myRef << std::endl;

--Rollie
But I didn't cheat. I wanted to write a custom allocator for my class, so I did.
(Obviously in a more complete example I would also write operator delete).
I didn't bend any rules.
yes rollie it did it,grrr!

I suppose one can get round anything.

but I'm only after silly mistakes not conscious effort.

jsmith: you did cheat (in my world) :-)

OK you lot win, templatized functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// (c) bigearsbilly

namespace useful {

class empty {};

template<class T, class S>
void unshift(T& C, S& s) {
    C.insert(C.begin(), s);
};

template<class T, class S>
void push(T& C, S& s) {
    C.insert(C.end(), s);
};

template<class T>
typename T::value_type
pop(T& C) { if(C.empty()) throw empty();
                typename T::iterator it = C.end();
                --it;
                typename T::value_type  s = *it;
                C.erase(it);
                return s;
                };

template<class T>
typename T::value_type
shift(T& C) { if(C.empty()) throw empty();
                typename T::iterator it = C.begin();
                typename T::value_type  s = *it;
                C.erase(it);
                return s;
                };

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <useful/push.hpp>
int main(int argc, char ** argv) {

    string s;
    vector<string> v;

    while(cin >> s) {
        useful::push(v, s);
    }

    try {
        for(;;) cout << useful::pop(v) << endl;
    } catch (useful::empty) { }
    cout << endl;

}


I'm starting to like this language.
bigearsbilly wrote:
I know one is not supposed to derive from STL containers
because they have non-virtual destructors.

You can always use composition instead of inheritance in such cases.
I guess, but with composition I'd have to
write a load of forwarding methods wouldn't I?

life's too short, innit?

the beauty of OO is inheritance after all.
bigearsbilly wrote:
I guess, but with composition I'd have to
write a load of forwarding methods wouldn't I?

True.

bigearsbilly wrote:
life's too short, innit?

Not true IMO. Life is exactly as long as you want it to be ;) If you want to live forever, so shall it be :)

(I'm not trying to convert you or anything but take a look here and tell me what you think:
http://cplusplus.com/forum/lounge/24744/ )

bigearsbilly wrote:
the beauty of OO is inheritance after all.

Yeap, I think inheritance is great! If you ask jsmith aka boost-boy (you'll understand why in time...) he'll tell you that maybe I decided to learn c++ just because it supports this feature :D
You took the words right out of my mouth :)

Actually, if you ask a colleague of mine (to whom I owe mostly every I know about C++),
he'll say that inheritance never delivered the promised of object-oriented programming.

It is true that you have to write a lot of forwarding functions, assuming your object has
the same interface as the underlying container. That may or may not be the best design.
But anyway, yes, it is typing. The good part is that it is code that can hardly be written
incorrectly. If writing bug-free code were as simple as writing more code, I'd do it in a
heartbeat.
Topic archived. No new replies allowed.