Advertisement

Unnecessary C++ features?

Started by July 13, 2000 07:53 AM
70 comments, last by null_pointer 24 years, 5 months ago
1. What is the difference between a typedef and a struct?

a typedef creates an alias for a type, e.g.

typedef matrix MyMatrix; 


MyMatrix is now an alias, and you can use it to create new instance of that type e.g.

MyMatrix aMatrix; 


is the same as

matrix aMatrix; 



A struct creates a new type, e.g.


struct MyStruct
{
int i;
float f;
std::string str;
};
<br><br>and you can create instance of ''MyStruct'' like this<br><br>[code]<br>MyStruct aStruct;<br>aStruct.str = "Hello";<br> </pre> <br><br><br>2. What do you all use STL for?<br><br>I use it whenever i need to use a collection of data, which happens pretty oftern is most of the programs i''ve done so far.<br><br>3. (Back to game development)Should I use inherited classes and virtual functions for different units, or just one class with a bunch of switches in the code? On the one hand, I have to delete and recreate each time if I use inherited, and some other headaches, but on the other hand the code is more intuitive and readable.<br><br>Personally I don''t like switches very much, so I''d go for the classes + virtual functions approach. It''s not very likely that the virtual functions will have much of speed impact (unless they''re used badly or extremely often). Switches just end up being to much work. For example if you want to add a new projectile weapon to your game, you can either go and find every switch you have then add the logic for the new weapon. If default: calls assert(fasle) your probably not going to miss, but it means your weapon logic is spread out all over the place. If you uses class + virtual functions, you can just do something like<br><br><pre><br>class rocket_launcher : public projectile_weapon<br>{<br> ...<br>};<br> </pre> <br><br>and use rocket_launcher wherever a weapon can be used.
First of all, I'd like to apologize if I am repeating anything someone has said in later replies, as I am replying to this reply only without having read the others first...

quote: Original post by c++freak

The name says it all .
Anyways, var1 += var2 is actually, performance wise, better then var1 = var1 + var2, because in the first, you do 1 addition, 1 'get' and one 'set', whereas in the second, you do 1 addition, 2 'get' and one 'set'. Ok tiny performace, but if you do it 1000 times that adds up... right....


Umm, no. += is just a shorthand for the usual addition. There is no difference whatsoever in the way the code is assembled. You think a CPU can add right from memory? Think again!

quote: Original post by c++freak
C++ keywords.
Well, now. C has them too. So they can't be unnecessary. (NO if's else's are you CRAZY!) where are you going to branch code? No user input! IMHO!

The & syntax whatever.
This is important, to you Performance buffs. Very necessary. Take function x:
void x(ABigClass y) {}
Function x must copy y every time it is run. You can't change y, either (well you can, but the changes will be lost). So to get around this:
void x(ABigClass* y) {}
Which is at the assembly level nearly the same as:
void x(ABigClass& y) {}
but without all those ->. Usual compiler implementations define & as simply a pointer to a point in the memory, with the stack on the calling sides of the function setting up the pointer to point to the argument, and the function side setting up a temporary argument that uses the pointer. Saves all that copying around. I use it mainly for operators, saving copying every time I do:
M = M*View.
or whatever.

Come on. You can't possibly think & is any different than *, as far as assembled code! "but without those ->" ??? In my opinion, it's clearer to use the * notation because it removes any confusion as to whether mystruct.member modifies a value somewhere else or not. When you see mystruct2->member , it's immediately clear that mystruct2 is a pointer (it even looks like an arrow... and arrows are used to point!). Also, when you're calling a function with a referenced argument, you have no idea if you're sending in a pointer or not.

        b = x(y); // you don't know if 'y' is going to be modified or not!!b = x(&y); // now you know 'y' COULD VERY WELL BE MODIFIED, as you're clearly sending its address    


So, I think the & (reference operator) is unneccesary, as it provides no additional functionality - it only adds confusion and potential maintenance nightmares.

BTW, I look forward to reading the rest of this thread!

Edited by - foofightr on July 15, 2000 6:17:04 PM
Advertisement
quote: Original post by POINT

someone said something about declaring variables anywhere in the code is useless...i disagree.

eg:
void func(int num)
{
int anArray[100000]; //in C you gotta declare the array at the top

if(num < 10)
return;

//...impl
}

if num is 3, then you just wasted your time allocating all that memory for the array.


Sorry to dissapoint you, but local variables, regardless of where they are placed in C++ code, are allocated at the beginning of the function. The only use I can see of this C++ feature (declaring vars virtually anywhere) is that it helps code readability if you declare a variable right before it is used (proximity of use).

Your 100000 ints are still allocated in C++ if you do this:

        void func(int num){    if(num < 10)        return;    int anArray[100000]; // still allocated regardless    //...impl}                

quote:
Umm, no. += is just a shorthand for the usual addition. There is no difference whatsoever in the way the code is assembled
[/quote[

This is true for basic types, but not for user defined types.


std::vector v1, v2;//fill v1 & v2v1 = v1 + v2; 


is not the same as

std::vector v1, v2;//fill v1 & v2v1 += v2; 




So, I think the & (reference operator) is unneccesary, as it provides no additional functionality - it only adds confusion and potential maintenance nightmares.


What about for operators in classes? Which block of code is more confusing when neither variables are pointers?

a = b; 


or

a = &b 


If you pass the address of something, you expect it to be modified, but what if your only passing by pointer to save creating massive temporaries (e.g. std::vector(1000)), and which function is clearer:

void f1(const std::vector& vec){	cout << vec[0] << endl;}void f2(const std::vector* const vec){	cout << (*vec)[0] << endl;}  



quote:
The only use I can see of this C++ feature (declaring vars virtually anywhere) is that it helps code readability


Only the _space_ for local varribles are allocated, so these two sections of code are very diffrent:

void func(int num){    if(num < 10)        return;    my_class_with_complicated_ctor* anArray = new my_class_with_complicated_ctor[100000]; 	//impl…} 


and

void func(int num){    my_class_with_complicated_ctor* anArray = new my_class_with_complicated_ctor[100000];     if(num < 10)        return;	//impl…} 


In this case the space for one pointer is allocated when the function is called, the massive array isn''t created unless the test (num < 10) fails.
quote:
Original post by foofightr

Umm, no. += is just a shorthand for the usual addition. There is no difference whatsoever in the way the code is assembled. You think a CPU can add right from memory? Think again!




Ok, maybe a=a+b and a+=b are the same thing, as I can''t seem to remember a CPU who can add right from memory. Maybe people are confusing this with a=a+1 and a++ . However, I believe all compilers nowadays do this kind of optimization, so it shouldn''t be an issue, really.
Maybe it meant less work for the people who were building the first C compilers
Gaiomard Dragon-===(UDIC)===-
quote: Original post by Wilka


Only the _space_ for local varribles are allocated, so these two sections of code are very diffrent:

void func(int num){    if(num < 10)        return;    my_class_with_complicated_ctor* anArray = new my_class_with_complicated_ctor[100000]; 	//impl...}  


and

void func(int num){    my_class_with_complicated_ctor* anArray = new my_class_with_complicated_ctor[100000];     if(num < 10)        return;	//impl...}  


In this case the space for one pointer is allocated when the function is called, the massive array isn''t created unless the test (num < 10) fails.


The difference here is that you no longer have just a declaration, you have an assignment statement to go with it. Apples and oranges.
Advertisement
quote:
The difference here is that you no longer have just a declaration, you have an assignment statement to go with it. Apples and oranges.


Good point, but if you take away the pointer it still makes a difference because the ctor isn''t called until it''s reached:

void func(int num){    if(num < 10)        return;	my_class_with_complicated_ctor anArray[100000];	//impl...} 


The complicated ctor could be creating a few thousands variables on the heap, so even if you only had of these classes it would still be better to declare a variable after any return tests. And the same for goes for assigning a variable, if you don''t create a variable when you know the value you want it to have can construct it from that value (or values) instead of using the default ctor then assigning latter.

If your using the ctor to allocate slow resources (like a network connection, or a file) waiting until you need the variable before you create it makes a big difference:

void foo(int n){	if(n < 10)		return;	my_network_class network;	//impl...} 
BeanDog:

#2 STL usage

Anytime you need to manage a collection of objects, like linked lists, queues, arrays, maps, etc. STL's got 'em. Besides, STL is coded in templates (hence the name Standard Template Library), and it's very fast because it uses templates. Also, STL doesn't require modifying your classes to work with its containers (except maybe a copy constructor/assignment operator), which keeps your code pretty small.

So say you have this class:

struct color{typedef byte unsigned char;byte red;byte green;byte blue;};  



In order to create a doubly-linked list with it in C, you would normally do something like this:

struct color{typedef byte unsigned char;byte red;byte green;byte blue;color* prev;color* next;};  



With STL, you can keep that stuff out of the class definition. STL automatically creates classes to hold that data for you, so when you need a list, you just do this instead:

list&ltcolor> colors;  



And that's it. You can iterate through it, or use the overloaded operator[] to use it as an array. When you want to use that color class with a dynamic array instead (for faster access), you would do this:

vector&ltcolor> colors;  



Of course you can use that vector just as if it were an array of color objects, but you can also use the vector member functions. vector contains functions to set the array size, insert members while automatically re-sizing the array, calculating the fastest new size, etc.

STL takes care of all the complicated stuff you really don't want to worry about when you just want to store your data. Of course, all of the insertion functions tell you what basic formula you can use for insertion times, etc. so if you need the STL classes for performance-critical code the information is all there.


BTW,

You could write a container module in C, but it's either not very flexible (can use only one data type), or it's not type-safe (uses void*). Besides, all the casting from void* is going to slow down performance a bit, no?

You could use macros in C to emulate templates, but that is messy. Besides, without the overloaded operators the client code is less intuitive and more complex. And what about inline functions? You would have to use macros for those, too, if you want the speed.

STL is incredibly fast and very easy to use. Learning it is another matter. I suggest you find a reference on the web or in a bookstore, or post questions about the different classes here.

Basically, learning your first STL class is hard, but all the other classes use similar concepts so you get a feel for the design of the library. Start with vector, since it's basically an array (which every programmer should be familiar with) and you don't have to learn how to use iterators.


Also part of STL is the stream operators that everyone seems to pick apart. (hey, it's their loss)

Basically, overloaded the operators and using templated operators allows easy, type-safe reading and writing. Plus you can overload those operators for your own data types, so their extensible. Streams are also very flexible concept, as they can send the data to a file or across a network (provided a network stream is written) with no change in the usage of the stream and thus no change in your code.

When I wrote my 2D tile-based map editor, I used STL streams. Simply write an input and ouput operator for each class, and then you can use those operators just like with the intrinsic types.

When I need to read or write a map object from a file (including tile layers, image file names, objects, scripts, etc.) I simply do this:

file << my_map;// sometime later...file >> my_map; 



And all the details are handled by the operators that I have written. Simple, no?


#3 switch vs. virtual

Here's what I do. If I have to switch on the "type" of the object or something used to indicate the "type" (like an ID byte), then it's a good time for inheritance and virtual functions.

If you find that you cannot make the virtual functions work, or it's very messy, then you might need to re-think your design. NP! That just means that somewhere along the line you have created more work for yourself than is necessary, and once you find that you have both better code and better understanding of the problem domain!


Happy Coding!



- null_pointer
Sabre Multimedia


Edited by - null_pointer on July 16, 2000 8:24:25 AM
quote:
Besides, all the casting from void* is going to slow down performance a bit, no?


Nope, other than dynamic_cast, all casts happen at compile time so there''s no runtime speed difference from using void* or T*. But there could be a very big development time speed difference when using void*. When using templates and safer casting (e.g. static_cast) a lot of bugs that would otherwise be hard to find runtime problems are compile errors that you find and fix straight away.

STL:

It''s a lot more than just some fast & safe data structures. Once you''ve got the hang of working with list, vector & map you should probably have a look at some of the algorithms and funtors. for_each + mem_fun1 + bind2nd comes in handy very often.
Wilka:

Aren't temporary variables created when you cast?



This code illustrates what I am talking about:

void main(){int x=0;float f = float(x);}  



Three variables are created in that function: x, f, and unnamed_float (for lack of a better term). unnamed_float is created, and then the statement:

float f = float unnamed_float(x);  



is executed, using "float& operator=(float, float)". Correct? At least that is how I understand the new casting syntax to mean, and my book seems to say the same thing. Maybe I'm just misunderstanding it.

Since pointers are just variables, the following example illustrates the same point:

void main(){int x;long* p1 = long*(&x);}  



That example creates 2 pointers - one temporary (and unnamed) and of course p1.

Now, what happens with this example?

struct mydata {int x;float f;};struct list_node {void* data;void* next;};list_node head;void main(){mydata data1;head->data = void*(&data1);list_node node2, node3;mydata data2, data3;node2->data = void*(&data2);node3->data = void*(&data3);head->next = void*(&node2);head->next->next = void*(&node3);}  



I count:
mydata - 3 instances, all local
list_node - 3 instances, 1 global and 2 local
void* - 5 instances, all temporary
mydata* - 3 instances, all temporary
list_node* - 2 instances, all temporary

Is this correct?





- null_pointer
Sabre Multimedia


Edited by - null_pointer on July 16, 2000 6:00:39 PM

This topic is closed to new replies.

Advertisement