Sign in to follow this  

Questions regarding moderate level C++

This topic is 4663 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've once again started towards trying to shake off some of my C habits and learn some of the more C++ specific conventions. As usual it's only brought more questions and headaches. So, on to the questions, in no particular order: 1 - I first started looking at C++ strings. I imagined they'd be the simpilest, and provide the most direct utility, allowing me to gain motivation for learning more. Well, I was right on the first part. They certainly are simple it seems. So, from what I can tell, they only take simple assignment, concatination, insert/remove, and find. Is that it? Is there no fancy automatic conversion like cout allows with multiple types? What exactly does this gain over char*'s beyond automatic bounds control and memory management [admittedly non-trivial]? And I'm curious as to the bounds control since char*'s will still be needed as an intermediary for the conversions... I also found some references to stringstream or strstream, which might be the same class, or not... That seems to allow the magical iostream formatting. Either way, only strstream seems to exist on my machine, and even then passing it to cout doesn't do the expected. I hope I'm simply missing something, and C++ strings -do- kick ass and take names, but right now it's not looking like it. 2 - Part of my problem with the STL I wager is my lack of experience with templates. Part of my problems learning templates is the trival examples that are always given with them. What sort of slightly more than trivial problems would be good practice problems for a template solution? The two that I came up with, a hash table with templated keys and a serialization setup, both ran across blocking problems which would've modified the design to be less featureful than their current non-templated incarnations. [aside: The hash table ran across a problem that the function pointer contained within to hash the key could not be used, as something like this:
template <typename K>
hash<K>::hash(int inbuckets=DEFAULT_BUCKETS, int (*in_algo)(K *,int
    =&default_hashing_algorithm(K *,int));
Which is perhaps beyond even the 'slightly more than trivial' I was shooting for, but... The compiler came back with "sorry, not yet implimented." and something about dump_* on this line. I didn't imagine something like that was feasible anyways, and proceeded to other things. Serialization ran into the problem that functions cannot be overloaded by return type only.] Both of them probably could've been worked out with more persistance and perhaps designing around the templates rather than converting them. Still, I'm not too convinced that templating would've really helped too much in either case. Still, I think templating is something I'm far too inexperienced with, and history has shown I need something very practical to really *get* something [linked lists for pointers, scene graph for inheritance]. I'm sure I'll have more once I actually get to the main part of the STL, if I don't get frustrated and avoid it again.

Share this post


Link to post
Share on other sites
Just a quick reply, as I'm passing through.

The book that opened my eyes to templates was Modern C++ Design; a link can be found here; this link contains a couple of sample chapters from the book, which may give you some template ideas.

As for string - I admit to only using it's basic functionality - but as I understand it, the functionality is greater than you're describing - essentially they're a container class of chars, so many of the STL algorithms are compatible with them. For example, I use string names to load resources from a factory class - the factory stores a map of file extensions and associated loading type - so it first finds the last bit of the string (i.e. .bmp) and then chooses the appropriate loader (class LoadBMP). This is routine to do with strings, like so:


std::string::size_type index = filename.rfind('.');
std::string extension = filename.substr(index + 1);
theResourcesTypedef_::iterator it = theResources_.find(extension);



HTH,
Jim.

Share this post


Link to post
Share on other sites
It sounds to me like you need this book: "C++ for Game Programmers" by Noel Lloepis. It's excellent, and it's sole purpose is to cover how to effectively use the exclusive features of C++. Templates actually has an entire chapter devoted to it.

In the book, he writes a linked list class using templates as his main example.

Here's his class declarations so you can see templates being applied to something slightly more advanced and useful:


template<class T>
class ListNode {
public:
ListNode(T);
T & GetData();
ListNode * GetNext();
private:
T m_data;
};

template<class T>
class List {
public:
ListNode<T> * GetHead();
void PushBack(T);
// ...

private:
ListNode<T> * m_pHead;
};

// here's some examples of using the List class:
List<int> listOfIntegers;
List<string> listOfStrings;
List<GameEntity*> listOfGameEntities;



I'm sure you can see how incredibly useful templates are in this case. Without the List and ListNode classes being templatized, you would have to rewrite both the classes for every single type of data you might want to use them to store. Plus, doing this would rule out the possibilty of using polymorphism (which is VERY useful for storing a list of game entities).

Well I hope this helps,
Zach.

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
1 - I first started looking at C++ strings. I imagined they'd be the simpilest, and provide the most direct utility, allowing me to gain motivation for learning more. Well, I was right on the first part. They certainly are simple it seems.

So, from what I can tell, they only take simple assignment, concatination, insert/remove, and find. Is that it? Is there no fancy automatic conversion like cout allows with multiple types? What exactly does this gain over char*'s beyond automatic bounds control and memory management [admittedly non-trivial]? And I'm curious as to the bounds control since char*'s will still be needed as an intermediary for the conversions...

I also found some references to stringstream or strstream, which might be the same class, or not... That seems to allow the magical iostream formatting. Either way, only strstream seems to exist on my machine, and even then passing it to cout doesn't do the expected.

I hope I'm simply missing something, and C++ strings -do- kick ass and take names, but right now it's not looking like it.


strstream is deprecated, this was a pre-standard class, std::(i/o)stringstream are the ones you want, they are actually type aliases (typedefs) for templated classes just as std::string is, they are independent of specific character type (and some cases allocation scheme too) there-for can be used for localization. An e.g of using a stringstream is:


#include <string>
#include <sstream>
#include <iostream>

int main() {

std::string s("1.3 10000");

std::istringstream iss(s);

float f; int i;

iss >> f >> i;

std::cout << "float: " << f << " int: " << i << std::endl;

std::ostringstream oss;

oss << "hi " << f << ", " << i;

std::cout << oss.str() << std::endl; //"str" returns a C++ string

}


a more exotic example:


#include <algorithm>
#include <iterator>

#include <vector>
#include <string>

#include <sstream>
#include <iostream>

int main() {

const std::vector<int>::size_type N = 20;

std::vector<int> v;
v.reserve(N);

std::generate_n(std::back_inserter(v), N, std::rand);

std::stringstream ss;

std::copy(v.begin(), v.end(), std::ostream_iterator<int>(ss, ", "));

std::string s((std::istreambuf_iterator<char>(ss)), std::istreambuf_iterator<char>());

std::cout << s << std::endl;

}


Not very useful example just shows you whats possiable, if you need something more advance check up boost's string algorithms library.

By the sounds of things you have an old compiler that isn't very standard compliant time to upgrade [smile].

Quote:
Original post by Telastyn
2 - Part of my problem with the STL I wager is my lack of experience with templates. Part of my problems learning templates is the trival examples that are always given with them.

What sort of slightly more than trivial problems would be good practice problems for a template solution?


Lots but if your knowledge of templates is no more than a the "container of T" its time to investigate, as already mentioned by JimPrice that is one of the best books to start looking, some keywords to look into:

static polymorphism (compile-time polymorphism), type traits, meta-template programming, expression templates, compile-time dispatch, Policy-Based Class Design, automatic type erasor, functors, Domain-Specific Embedded Languages (DSEL a special little language with-in C++ built out of C++ [smile]), (off the top of my head). Look into the boost library and loki library.

You'll probably need to know about templates & functors fully before moving on to the above techniques and compiler that supports it properly especially partial template specializations that is very important.

Quote:
Original post by Telastyn
Still, I think templating is something I'm far too inexperienced with, and history has shown I need something very practical to really *get* something [linked lists for pointers, scene graph for inheritance].


Yeah by the sounds of things you might actually be better off starting with something like The C++ Programming Language Special Edition (i'm not trying to be patronizing, its really good for learning C++ quite deeply) then moving on to more advance books like the one JimPrice suggested.

Share this post


Link to post
Share on other sites
Thanks for the replies so far!

I've not delved into STL algorithms much yet, but don't the algorithms already apply to arrays? And shouldn't they already work with char arrays? Something to at least keep in mind though :]

A templated linked list is the standard trivial example. I was looking for something a little more. I've a non-templated linked list class I'm currently using, and templated lists show no clear cut advantage over it. Further, the STL will provide any templated data structure I'd want...

And more questions!

With STL container classes, storing pointers is 'safe', within normal pointer practices, correct? And I am responsible for free()/deleting the pointer, as the container will not do that, correct?

With iterators, is the standard iteration:

for (iterator=container.begin(); iterator!=container.end(); ++iterator){
...
}

?

Does container.end() get called each iteration, or is that something which will be optimized out nicely. Is there a .bad() check for the iterator that should be used instead?


More later.

[edit: snk_kid: Ah yes, probably about 6 years old now... Anyway, thanks for the example, I had merely forgotten the () for sstream.str.

Further, the first type traits link I found was *very* helpful in providing a good practical glimse at what templates can do that is otherwise impractical!]

[Edited by - Telastyn on March 7, 2005 6:50:19 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
I've not delved into STL algorithms much yet, but don't the algorithms already apply to arrays? And shouldn't they already work with char arrays? Something to at least keep in mind though :]


Sure, there generic any raw C-style array will do.

Quote:
Original post by Telastyn
With STL container classes, storing pointers is 'safe', within normal pointer practices, correct? And I am responsible for free()/deleting the pointer, as the container will not do that, correct?


Yep, generally its actually more efficent to store by value than by reference (it really depends on the context how-ever in some cases its not possible or feasiable to, for e.g polymorphic types can only behave polymorphically via storing pointers) and all STL containers are parameterized my allocator type (last template type parameter) so you can even use a pooling scheme.

Quote:
Original post by Telastyn
With iterators, is the standard iteration:

for (iterator=container.begin(); iterator!=container.end(); ++iterator){
...
}

?

Does container.end() get called each iteration, or is that something which will be optimized out nicely. Is there a .bad() check for the iterator that should be used instead?


It's generally preferred to use the standard library algorithms over hand-written loops for a number of reasons that are beyond the scope of this thread [smile].

Yes in most cases end will be repeatedly called in that code and post-incrementing iterators (i know your not just illustrating a point) actually costs performance this is just one out of many reasons to perfer the STL algorithms, if your doing any operation that does not invalidate iterators and not modify the container you can make a cache of end e.g.


for(iterator itr = container.begin(), end = container.end();
itr != end; ++itr) ;


By the way you may want to re-read my previous post because i corrected mistakes and added some more things [smile]

Share this post


Link to post
Share on other sites
Quote:

I've not delved into STL algorithms much yet, but don't the algorithms already apply to arrays?


You know, I don't know. Now I need to do some reading. And I was having such a pleasant afternoon as well[smile].

Quote:

With STL container classes, storing pointers is 'safe', within normal pointer practices, correct?


Yup, with some caveats (that caught me out the first time I wrote an A* path-finder).

Some containers move themselves wholesale under some conditions (the standard example is std::vector, which normally uses an array as it's underlying implementation - and when you try and resize it beyond it's current capacity, the whole thing is moved, as opposed to any appending. This means that pointers to elements in the std::vector no longer point to what you think they're pointing at...).


Quote:

And I am responsible for free()/deleting the pointer, as the container will not do that, correct?


Entirely correct. Destruction of the container is guaranteed to run the destructor of each element, but if it's a pointer it just means you lose the pointer to the allocated memory. Leak-tacular. Of course, you could have containers of smart-pointers, which would go someway to alleviating this problem.

Quote:

With iterators, is the standard iteration:


for (iterator=container.begin(); iterator!=container.end(); ++iterator){
...
}


?

Does container.end() get called each iteration, or is that something which will be optimized out nicely. Is there a .bad() check for the iterator that should be used instead?


Yup, although the iterator needs classifying (that is, a std::vector<int>::iterator is different to a std::vector<char>::iterator - it won't automatically pick up the container type).

The alternative solution is to use an algorithm - the normal example is for_each, as follows:

std::for_each(container.begin(), container.end(), something);

where 'something' may be a function, functor or member-function. There's some funky syntax that comes up with this though, under certain condition (like bind2nd and that sort of thing).

According to my reading of Meyers Effective STL, container.end() may well get called every iteration - don't know if it will be optimised away. This is one reason for advocating use of algorithms, although there are others. For example, algorithms are usually partially specialised on container types to provide more efficient iteration through through the container than you get with your loop as written.

Not sure what you mean by a .bad() check? container.end() points to one element past the last element in your container, a handy little feature that means that the loop automatically won't evaluate if the container is empty. Or are you talking about modifying the elements of the container during iteration?

As always, a useful reference for the STL is the SGI website.

HTH,
Jim.

Edit : oops, whatever snk_kid said (I know my place.....)

Share this post


Link to post
Share on other sites
Quote:

With STL container classes, storing pointers is 'safe', within normal pointer practices, correct? And I am responsible for free()/deleting the pointer, as the container will not do that, correct?


Pointers are fine. You must release the pointer manually (clear() or erase() won't do it for you) as you have assumed.

Quote:

(snipped code)

Does container.end() get called each iteration, or is that something which will be optimized out nicely. Is there a .bad() check for the iterator that should be used instead?


That depends largely on the compiler. I imagine if the compiler can determine that a function invoked from the guard condition has a cacheable result, it will cache it instead of calling the function every time. Which basically means the answer to your question is "maybe," depending on the compiler and the container type (some may have more trivial implementations of end(), et cetera).

[EDIT: Actually, I imagine the C++ standard dictates whether or not functions in the guard need to be called every time, or can be cached. I don't know where to find a copy online though -- anybody know?]

There is no bad() method for iterators -- that'd be a chore to implement, and might be semantically a bit awkward, as end() returns an interator that is actually one element beyond the last in the container.

[EDIT: I type slow. :( ]

Share this post


Link to post
Share on other sites
Quote:
Original post by JimPrice
Quote:

With iterators, is the standard iteration:


for (iterator=container.begin(); iterator!=container.end(); ++iterator){
...
}


?

Does container.end() get called each iteration, or is that something which will be optimized out nicely. Is there a .bad() check for the iterator that should be used instead?



Not sure what you mean by a .bad() check? container.end() points to one element past the last element in your container, a handy little feature that means that the loop automatically won't evaluate if the container is empty. Or are you talking about modifying the elements of the container during iteration?


For iostreams, the iostream should be checked with .bad() or .is_good(), which encapsulates IO error, or eof or anything else. I was just wondering if there was a similar syntax, even the iterator being null... the current container class I use has a syntax like this:


container *c;
// stuff...
for (container *ptr=c; ptr ; ptr=ptr->next){
//more stuff...
}


so my thinking is rather stuck on an invalidated [for whatever reason] iterator being not any value, rather than equating to a special value (despite those statements being fairly equivalent).

Share this post


Link to post
Share on other sites
I forgot mention because its such a pain to work with pointers, deal with object owner-ship, and STL containers together look up *smart* pointers the boost library provides a package of smart pointers, not only will they delete objects for you (among being reference counted/linked and other nice stuff) at the wright time you also get RAII for free.

Quote:
Original post by Telastyn
Further, the first type traits link I found was *very* helpful in providing a good practical glimse at what templates can do that is otherwise impractical!]


Don't be so quick to judge you just need some inspiration [smile]

You've probably seen some type trait examples now for real-world uses, i'm going to use boost type traits library (it's in C++ TR1 library so should become part of the standard library soonish):


#include <boost/type_traits.hpp>
#include <cstddef>
#include <cstdlib>
#include <iterator>

template < bool > struct my_copy_imp; //forward declaration

template <class InputIterator, class OutputIterator>
inline OutputIterator my_copy(InputIterator first, InputIterator last,
OutputIterator result) {

typedef typename std::iterator_traits<InputIterator>::value_type in_val_t;
typedef typename std::iterator_traits<OutputIterator>::value_type out_val_t;

const bool CopyAlgorithm = typename ::boost::is_pointer<InputIterator>::value &&
typename ::boost::is_pointer<OutputIterator>::value &&
typename ::boost::is_pod<in_val_t>::value &&
typename ::boost::is_pod<out_val_t>::value;

return my_copy_imp<CopyAlgorithm>::do_copy(first, last, result);
}

template <>
struct my_copy_imp<true> {
template <class InputIterator, class OutputIterator>
static inline OutputIterator do_copy(InputIterator first, InputIterator last,OutputIterator result) {
std::size_t N = last - first;
return static_cast<OutputIterator>(std::memcpy(result, first, N * sizeof(std::iterator_traits<InputIterator>::value_type)));
}
};

template <>
struct my_copy_imp<false> {

template <class InputIterator, class OutputIterator>
static inline OutputIterator do_copy(InputIterator first, InputIterator last, OutputIterator result) {
for(; first != last; ++first, ++result)
*result = *first;
return result;
}
};

//example
#include <cmath>
#include <algorithm>
#include <iostream>

int main() {

const size_t N = 30;
int foo[N];
int bar[N];

std::generate_n(&foo[0], N, std::rand);

my_copy(&foo[0], &foo[N], &bar[N]);

std::copy(&bar[0], &bar[N], std::ostream_iterator<int>(std::cout, ", "));

}



See there are too implementations of my_copy a fast one for POD-types and conservative one for non-POD-types choosen at compile-time and this is all inlined away, this is what alot of modern implementations of the c++ standard library do stuff like the above so you've seen a glimpse of it now [smile].

Did i mention i updated my first post? if you have any Qs on code just ask.

EDIT: corrected mistake in code .

[Edited by - snk_kid on March 7, 2005 7:33:00 PM]

Share this post


Link to post
Share on other sites
Hah, boost libraries and STL implimentations aren't exactly something in my real world.

I'm pretty decent at reading the code like you show, and don't have any questions except perhaps what generate_n takes as 3rd parameter. I assume it just takes int (*)() or even something like <templated> (*)(), but I'd actually have to lookup what std::rand is to know [and that's easy enough to do]. Just need to get experience actually using/thinking in ranges rather than procedural loops.

Er, nevermind. I saw the std::memcpy, and remember the cstdlib stuff will just prepend the namespace.

I am at least familiar with the concept of smart pointers, and have implimented a few ref-count setups in my current project. I just need to understand some of the implications of pointers with template setups, as pointers tend to need to know about those little nuances :]


So, another question then.

Why is length+1 used as .end() and the error value for things like find()? Like I said, I'm used to invalid iterators being null, which makes invalid iterator checking fairly easy. The GotW #18 linked above shows that things like find(begin,end,stuff) can/will return end(), which if passed along cannot be checked too easily.

In a related note, I've a tendancy to check input to functions [such as the passed along iterator above] rather than a return from functions. Since the functions need to be robust against input problems anyways, it's always made more sense to me to handle every non-fatal [or non-potentially_fatal] error on input rather than from return. I assume this is poor form, as nobody else seems to follow this practice, so I wonder why?

[And this all of course ignores exceptions, which I imagine will be top priority as soon as the STL starts tossing them at me...]

Share this post


Link to post
Share on other sites
Ah, finally got that sstream example to compile nicely. *nix packages are great, until they're not. And when they're not with libraries or compilers... well, they're really not great!

Thanks everyone for your replies so far. Hopefully my unprecidented motivation will carry unto the marrow.

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
perhaps what generate_n takes as 3rd parameter. I assume it just takes int (*)() or even something like <templated> (*)()


No, it takes generator type any C++ *callable* entity can be used in this case that takes 0 arguments and returns a type T, its generic it could be a free-function, a bound member function (some object with a member function pointer), functor, a lambda expression (with library help [smile]), simple example:


template < typename Func >
void foo(Func x) {
x();
}

#include <iostream>

struct bar { //functor

void operator()() const { //overloading function call operator
std::cout << "bar\n";
}

};

void bar2() {
std::cout << "bar2\n";
}

int main() {

foo(bar()); //using a functor
foo(bar2); //using a free-function

}


Whats happening here is the type of x is automatically deduced by the compiler, so the first statement makes a template instantion of Func == bar giving void foo(bar x) and the second one of Func == void (&x)() giving void foo(void (&x)()) (reference to a function, not pointer), the compiler can only deduce types for function arguments it can not deduce return types unless one of the function arguments is also the same type as the return type. Now because bar supports the operation () as does bar2 it compiles. This is also the reason why you can use raw C-style arrays with STL algorithms because a pointer is a model of the iterator concept, an iterator is also a generalization of a pointer.

The above is basically what is known as ad-hoc polymorphism and because type dispatch and binding occurs at compile-time in C++ its also static (compile-time) polymorphism, in thoery all dynamic polymophism can be replaced by static one but in the real-word its not feasiable for certain problem domains like GUIs where dynamic/runtime binding/type dispatch is needed.

Functors like the above (there are other kinds of functors like generalized functors another story) can be completely inlined away as there is no level of indirection as references/pointer to function do the only disadvantage is it must be passed by value (well its not that bad functors don't normally have complex large state so it doesn't matter) because if you used something like a constant reference to the type it you restict allowable types, its not generic, it wouldn't work for all kinds of callable entities.

Quote:
Original post by Telastyn
I am at least familiar with the concept of smart pointers, and have implimented a few ref-count setups in my current project. I just need to understand some of the implications of pointers with template setups, as pointers tend to need to know about those little nuances :]


My tip is not restrict the type to a specific kind of type such as T*, just use T and act like its a pointer then you can use any raw-pointer or smart pointer (not just your own).

Quote:
Original post by Telastyn
So, another question then.

Why is length+1 used as .end() and the error value for things like find()? Like I said, I'm used to invalid iterators being null, which makes invalid iterator checking fairly easy. The GotW #18 linked above shows that things like find(begin,end,stuff) can/will return end(), which if passed along cannot be checked too easily.


Well its not really an error its just an indication of not finding the value. My guess is the reason why null is not used because its not very generic, for example stream iterators there is no concept of null in a stream which could be connected to a network or a file, you may also have some kind of exotic iterator where null makes no sense too plus mix this with container iterators i think it makes perfect sense.

Quote:
Original post by Telastyn
In a related note, I've a tendancy to check input to functions [such as the passed along iterator above] rather than a return from functions. Since the functions need to be robust against input problems anyways, it's always made more sense to me to handle every non-fatal [or non-potentially_fatal] error on input rather than from return. I assume this is poor form, as nobody else seems to follow this practice, so I wonder why?

[And this all of course ignores exceptions, which I imagine will be top priority as soon as the STL starts tossing them at me...]


I think this has to do with STL algorithms choosing simplicitly to gain generalization and efficency at the same time which is a tough challenge in its self, just as the C libraries do usually input at run-time is expectated to be in a valid state and programmer is expectated to know what he/she is doing, of-course assertion are most likely used for debug builds and atop of that for STL algorithms you get static type checking by the compiler and in most cases concept checking too [smile]. STL algorithms don't throw exceptions (well not that i know of) but your iterators & types certainly can and some STL container operations throw them for example you can enable exceptions in IOStreams and use stream iterators, when a something bad happens you'll get an exception thrown out of an STL algorithm.

STL containers have requirements on exception safety i can't remember complete details but Bjarnes details it in his special edition book, most STL imps i've seen imploy some RAII techniques to gain some exception safety and other very advance techniques to improve efficiency like EBO on allocator types, separation of allocation/deallocation and construction/destruction (allocator concept).

[Edited by - snk_kid on March 8, 2005 9:00:21 AM]

Share this post


Link to post
Share on other sites
I thought i'd mention that you can certainly mix static & dynamic polymophism and its where things get very interesting and useful, for example you could automagically generate an interface hierarchy then generate an implementation hierarchy thats implement all of those interfaces, you don't write code any more you write code that generates the code :/ so not only is this generic programming, meta-template programming but also generative programming.

Another example of mixing techniques is as i mentioned earlier automatic type erasor (i know i can't spell :P), most people would have some kind of resouce manager & resource type, the resource type is usually choosen to be an abstract type so you can have resource of any type but you have to manually derive and implement for each new type resource this is known as manual type erasing, with templates you can make it automated e.g.


#include <memory>
class resource {

struct IResImp {
virtual ~IResImp() = 0;
virtual IResImp* clone() const = 0;
};

template < typename T >
struct ResImp : IResImp {

typedef T value_type;

T res;

ResImp(const T& res = T()): res(res) {}

ResImp<T>* clone() const { return new ResImp<T>(*this); }
};

std::auto_ptr<IResImp> res_ptr

public:

resource(): res_ptr() {}

resource(const resource& r): res_ptr(r.res_ptr->clone()) {}

template < typename T >
resource(const T& t)
: res_ptr(new ResImp<T>(t)) {}

resource& operator=(const resource& r) {
if(this != &r) {
res_ptr.reset(r.res_ptr->clone());
}
return *this;
}

template < typename T >
resource& operator=(const T& t) {
res_ptr.reset(new ResImp<T>(t));
return *this;
}
....
};

#include <complex>

int main() {

resource int_res = 30;
resource complex_res = std::complex<float>(3.0f, 1.0f);

}



you can even store resource in STL containers happly, obviously it needs a little more work to be usuable and we can regain efficency we lost for using dynamic memory allocation via using poolling scheme or something simillar.

This technique is used in boost::any, boost::shared_ptr, and other places in slightly different ways.

Other techniques that mix the two is creation of multi-dispatch in C++, C++ only allows single dynamic type dispatch only in the language, for multi-dispatch at runtime you need to write it yourself, in the land of compile-time multi-dispatch is simple so combine them you can get a generic multi-dispath library for C++

More, generate containers of types at compile time (there just types), type-lists, type-sets/maps, type trees (look up boost mpl library as an example), use meta-template algorithms to manipulate them etc.

[Edited by - snk_kid on March 8, 2005 9:23:20 AM]

Share this post


Link to post
Share on other sites
Quote:
STL containers have requirements on exception saftefy i can't remember complete details but Bjarnes details it in his special edition book, most STL imps i've seen imploy some RAII techniques to gain some exception saftefy and other very advance techniques to improve efficiency like EBO on allocator types, separation of allocation/deallocation and construction/destruction (allocator concept).


The relevant appendix has been made freely available.

Share this post


Link to post
Share on other sites
Okay, okay. Slow down.

I got the functors example, and it was interesting, as I thought they'd be implimented slightly differently. I also kind of expect that more complex versions will impliment them slightly differently...

I've been using something like this for function passing:


struct guiaction{
void (*activate)(vine *,vine *);
vine *target;
vine *params;
void doa(){
activate(target,params);
}
}


where vine* is a... container class of variants? I hope that's the correct terminology. Nothing is templated though, so I imagine it's not nearly as nice or... versatile as it perhaps could be.

The exception discussion is over my head currently. I've at least read up on templates and followed these forums for some time, even if my experience is lacking. I've only touched on exceptions in passing, and they're not a common topic here...


Now, for the resource manager...

Just questions rather off the top of my head from looking at it.

This requires any resource stuffed into it to have a copy constructor made, correct? And that by placing the resource into the manager, it copies it, meaning anything you've loaded now should be disposed of?

And it was done that way so that pointers passed in will be handled as well, rather than making an assumption?

Why the IResImp? I'm guessing to allow template specialization or general inheritance for things more complex than ints? Things like textures perhaps that require load() and release() rather than the default destructor?

Why the typedef T value_type?

std::auto_ptr is just a standard reference counting class? And .reset() simply resets the count to 1? And exists in <memory> [if not, why was that included?]?


And the only way to generate the template expansions is by this sort of trickery, or explicit use, perhaps via #define's I'd wager? What I mean is there's no way to really store a type, or parameter to pass into template<variable>(stuff). And this is because the templates are generated at compile time, and variables exist at run time?

Share this post


Link to post
Share on other sites
Hrm, after more research, the typedef is used for STL erm... predefined function handling and stuff in the algorithms it seems.

And it seems the common trick to generate template classes is by using template functions and c++'s type picking [like the = member function in the resource manager example above].

Thanks once again for the help, and examples. Back to experimenting.

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
I got the functors example, and it was interesting, as I thought they'd be implimented slightly differently. I also kind of expect that more complex versions will impliment them slightly differently...


The definition of or what exactly a functor is will vary depending on who you talk to my above example was the simplest case in C++, at the end of the day a functor AKA a functional object is just function + state and in C++ creating one generally involves some class that has overloaded the function call operator, it can implementated as complicated as needed like some functor's are generalized such they are really generic versions of the classic Command design pattern, they can take arbitrary pointers to functions, pointers to member functions, other functors including itself with arbitrary signatures.

The thing is it doesn't matter how its implementated with templates you can use them all uniformly they could even have other member functions it doesn't matter as long as it supports the required operations that are used on that type in the template code its kinda of hard to explain :/ i hope that made sense.

Quote:
Original post by Telastyn
This requires any resource stuffed into it to have a copy constructor made, correct?


If you mean T requires a user-declared & defined copy constructor & copy assignement, it doesn't all c++ types are assignable & copyable (excluding those user-defined types where its disabled via explicitly declaring it in private access that means pointers the type should be used instead).


Quote:
Original post by Telastyn
And that by placing the resource into the manager, it copies it, meaning anything you've loaded now should be disposed of?


Yes if i understood you correctly, i wouldn't take the resource example seriously it was merly to demonstrate how to do automatic type erasing.

Quote:
Original post by Telastyn
And it was done that way so that pointers passed in will be handled as well, rather than making an assumption?


Yeah you can use pointers if you wanted but then responsibility of memory management becomes the clients (you can always use a smart pointer it would still work).

Quote:
Original post by Telastyn
Why the IResImp? I'm guessing to allow template specialization or general inheritance for things more complex than ints?


IResImp is part of the trick for type erasing, when class templates are instantiated each template instantiation is a completely separate type so if you made the resource type itself a template there wouldn't be anyway to store different resources in an STL container directly because its a container of a specific type there-for it would be container of a specific resource type.

IResImp is simply a way to ignore specific type like void pointers but it's more type-safe than void pointers and you can actually work with it where you can't do nothing with void pointers other than cast them to from other types, its not entirely safe so its hidden with a safe interface, state invariants can be maintained more easily.

The sub-type ResImp is class template it holds all the type information at the very last moment before we erase the type information, all the rich type information will be encoded in object instances of ResImp, the template member functions (the constructor & assignement operator) help to make it transparent and automated as i mentioned easlier function argument types are deduced by the compiler automatically and the template type parameter is also used for template instantiation of ResImp which is then an object instance of it is dynamically allocated and assign to the base pointer this is the precise moment you loose/erase type information.

Quote:
Original post by Telastyn
Why the typedef T value_type?


don't worry about it to much i didn't really need to put it there, how-ever nested typedefs are a convention and a gift to meta-template/generic/generative programming very useful tool you will see at some point.

Quote:
Original post by Telastyn
std::auto_ptr is just a standard reference counting class? And .reset() simply resets the count to 1? And exists in <memory> [if not, why was that included?]?


std::auto_ptr is not a reference counted smart pointer, its simply used to employ "Resource Acquisition Is Initialisation" (RAII) it provides some exception safety and will also delete the instance at the end of a scope so i did't need to have an explicit user-defined destructor.

The reason for using reset member function in the assignement operators is auto_ptr's object owership policy is to just transfer ownership on copy/assign so the default behaviour the implicitly defined assignement operator wasn't suitable in this context. This also means you should not use std::auto_ptr in STL containers there where never intended for that purpose.

Quote:
Original post by Telastyn
And the only way to generate the template expansions is by this sort of trickery, or explicit use, perhaps via #define's I'd wager? What I mean is there's no way to really store a type, or parameter to pass into template<variable>(stuff). And this is because the templates are generated at compile time, and variables exist at run time?


I think i've explained this one already :P

Quote:
Original post by Telastyn
Hrm, after more research, the typedef is used for STL erm... predefined function handling and stuff in the algorithms it seems.


Kind of really iterator traits (std::iterator_traits, remember type traits we where talking about, there is also the concept of character traits for I/O streams library) are used instead because only iterators are passed to STL algorithms not containers, user-defined iterator types also provide nested typedefs but even those are not directly used because the iterator type might actually be raw pointers which can never have nested typedefs so iterator traits are used instead making the code generalized/generic and it costs nothing.

Quote:
Original post by Telastyn
And it seems the common trick to generate template classes is by using template functions and c++'s type picking [like the = member function in the resource manager example above].


Yep it is, when i said boost::shared_ptr uses it i forgot to mention it doesn't directly use it, it only uses it for custom deleter types that clients can use for special cases.

There is much more thou, you will see some of it, explained better and in more detail in books like Modern C++ Design by Andrei Alexandrescu its usually the book that opens up eyes to people [smile]

Share this post


Link to post
Share on other sites
I have a quick question about the resource code posted above.

The clone method is:

ResImp<T>* clone() const {return new ResImp<T>(*this); }

and it is called in the copy ctor as:

resource(const resource& r) : res_ptr(r.res_ptr->clone()) {}

When the copy ctor is called, isn't *this a std::auto_ptr<ResImp> (ie r.res_ptr)? In which case, there is no appropriate ctor for ResImp. Does this mean either a ctor for ResImp taking a std::auto_ptr<ResImp> needs to be added, or the resource copy ctor needs to be changed to:

ResImp<T>* clone() const {return new ResImp<T>(this->res); }

Or am I missing something (probably)?

Thanks in advance,
Jim.

Share this post


Link to post
Share on other sites
Isn't it just a typo and supposed to be IResImp<T> ...?

I mean that shouldn't even comile, as the pure virtual IResImp... clone() isn't defined in a derived class [ResImp]?

Share this post


Link to post
Share on other sites
Quote:
Original post by JimPrice
The clone method is:

ResImp<T>* clone() const {return new ResImp<T>(*this); }

and it is called in the copy ctor as:

resource(const resource& r) : res_ptr(r.res_ptr->clone()) {}

When the copy ctor is called, isn't *this a std::auto_ptr<ResImp> (ie r.res_ptr)? In which case, there is no appropriate ctor for ResImp. Does this mean either a ctor for ResImp taking a std::auto_ptr<ResImp> needs to be added, or the resource copy ctor needs to be changed to:

ResImp<T>* clone() const {return new ResImp<T>(this->res); }

Or am I missing something (probably)?


The member selection operator dereferences a pointer before selecting a member, when it's dereferenced it's (depending on the context) of type ResImp<T>& or const ResImp<T>&, that technique is known as virtual constructor idiom

Quote:
Original post by Telastyn
Isn't it just a typo and supposed to be IResImp<T> ...?

I mean that shouldn't even comile, as the pure virtual IResImp... clone() isn't defined in a derived class [ResImp]?


It's not a typo, you can do this its called covariant return types and it can only be applied on virtual member functions in C++.

[Edited by - snk_kid on March 9, 2005 4:43:39 AM]

Share this post


Link to post
Share on other sites
Quote:

The member selection operator dereferences a pointer before selecting a member, when it's dereferenced it's (depending on the context) of type ResImp<T>& or const ResImp<T>&, that technique is known as virtual constructor idiom


So - presumably you're relying on the implicit copy ctor for ResImp (which would make sense, given it's only got a T member)?

Thanks for the help,
Jim.

Share this post


Link to post
Share on other sites
Quote:
Original post by JimPrice
So - presumably you're relying on the implicit copy ctor for ResImp (which would make sense, given it's only got a T member)?


Yep, the default behaviour is appropriate.

Share this post


Link to post
Share on other sites

This topic is 4663 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this