×

Loading...

C# is more error proof than C++

本文发表在 rolia.net 枫下论坛I always think C# (also Java ) is more advanced than C/C++. You may disagree with me, because our definitions of “advance” are different. My definition of “advanced” is something like “error proof”, not “powerful”. If you can agree C/C++ is more advanced than assembly, then you should agree with me. Code written in ASM normally can give faster speed and more accurate control/access of the hardware (eg. virus). There was a debate years ago on weather should switch to ASM to C, because it lose efficiency, and there also was a debate happened a little more recent about whether should switch from C to C++. You saw the answer. Now it’s time for C++ and C#. Each time switch, we lose some effiency, but I definitely agree with a kind of logic: in long term words, the efficency of human being is the biggest issue. That’s what behind the last 2 switch. Now a day, we are more and more manufacturing code than writing code, so we are needing more efficiency of human being. No matter you like it or not, that’s the trend.
C++ was designed several decades ago. It’s a bit harder for C++, because it need to maintain the backward compatibility with C.

char* p=”Hello Wolrd”;

code 1-1

Logically, this piece of code is wrong, because the character literal “Hello World” is constant, so at the left side of the equation, p should be as const char*. 1-1 deserve a compile error, but it never get it. Why? Because it’s so common to write code like this in C. If C++ decide to give it a compile error then million lines of C code written like this will fail to compile if they are compiled with a C++ compiler.
So, the C++ designer choose to tolorate it . As a result, if you try to modify the value through pointer p, the result is not defined (or you prefer the world “compiler defined”J).
But how good is the compilers’ ability of defining this? Have a look at VC6:

#include “stdio.h”
void main()
{
char* p=”hello”;
p[0]=’w’;
char* q=”hello”;
printf(q);
}
code 1-2

If you build 1-2 with VC in release mode, you will get a “wollo” printed, that’s probably not what you want. If you build it with debug mode, you get an unhandled exception when assigning value to p[0], because compiler put the “hello” in the read-only memory page which is protected by the Operating System.

Beside the pain from compatibility to C, C++ had to concern more of the efficiency issue then today’s C# or Java. This also give it lots constrains.


Needless to say, C++ is such a powerful language, the language feature such as template multiple inheritance, operator override… give you enough chance to write code very creatively. But remember, more sharp the knife is, more easy to cut yourself before you fully understand how to use it.

There is a very nice book from Scott Meyers talking about C++. It tells you where is the trap in C++. I found this book after one year of using C++ as my working language. When I was reading it , I got astonished, I almost dropped into every trap he mentioned . And each of them may cause my application crash on end user’s computer, or make me rewrite the whole inherit hierarchy if I have a design error in the base class.

How about these traps in C#? Let’s look at it.

Item 1:
Try to use const and inline instead of #define
The trap is:
#define MAX(a,b) ((a)>b?a:b)
if you call MAX(++a,4), then trouble comes. a++ will be executed twice, which maybe not what you want. This is called Macro side effect. So, Scott suggest you to use inline function instead of Macro.
What happen in C#? #define can be only used for conditional compiling, not for anything else. That’s exactly the only usage of #define suggested by Scott.

Item 5:
Use delete[] if you new [].

#include “stdio.h”
class a
{
private :
char* p;
public:
a()
{
p= new char[100];
printf(“constructor get called \n”);
}
~a()
{
printf(“desctructor get called \n”);
delete []p;
}
}

void main()
{
a* mya=new a[10];
delete mya;
}
code 1-3
what happened? You can see 10 constructor get called but only 1 destcuctor get called. In C++, new and delete is responsible for two things: 1. Allocate(Free) memory, 2 call constructor(desctructor) on it.
If you call “delete mya”, only the mya[0]’s desctrutor get called and so it’s the memory pointed by mya[0].p is freed. The memory of mya[1].p to mya[9] .p leaked.
What you should do is delete[]mya. This tell the compiler to generate code to call destructor for all the 10 mya element;

What happens in C#? It’s totally not an issue. C# has a garbage collection function which mean allocated memory needn’t be freed manually, the runtime will be responsible to free it correctly.

Item 9.
Some issue about overload operator new.

#include "new.h"
class X {
public:
void f();

static void * operator new(size_t size, new_handler p);
};

void specialErrorHandler()
{
}
void main()
{
X *px1 =
new (specialErrorHandler) X; // calls X::operator new

X *px2 = new X; // error!
}

Get surprised?
It’s because X*px2=new X; equal X* px2=X::operator new(sizeof(X)), but unfortunately, X::operator new take 2 parameter.

How about C#? C# support for operator overload, but not all operator can be overloaded,
Only + - ! ~ ++ -- true false and + - * / % & | ^ << >> == != > < >= <= can.
This is a well selected set.
In C++, you can overload ++ and --, but do you know how to overload a++ and ++a differently? Yes, there is a way. But not everyone knows, not every book tells.
In C++, you can overload || and &&, but do you know how to make the overloaded operator act like default one which support short circuit operation?
For example:
strcut A
{
int i;
};
A* a;

if(a!=0&& a->i>10)
in C++, if a==0, a->I>10 won’t be executed, so no tragedy will happen, can you make you overridden operator && behave the same?
C# chose a simple solution: forbid you to override them.

Item 11:
class a
{
char* data;
public:
a()
{
data= new char [100];
}
~a()
{
delete [] data;
}
};

void main()
{
a ax;
a bx=ax;
}


any thing wrong? Try with VC6 debug build, get an exception? Is VC crazy? No. The code is wrong.

When you do a bx=ax;
Compiler implicitly call a member function (copy constructor) which act like memcpy(&bx,&ax,sizeof(a)); yes, you didn’t write this, so compiler will generate a default one for you which not works very well.
When the main finished, all the stack object is going to be destructed, bx.~b() is called so, bx.data is deleted, unfortunately, dx.data=ax.data, so when ax’s destructor is called, the ax.data is being deleted again. That’s the excpetion comes from.

So, you should always override the copy constrcut (the same for the operator=) as this:

class a
{
char* data;
public:
a()
{
data= new char [100];
}
~a()
{
delete [] data;
}
a(const a& ax)
{
data=new char[100];
memcpy(data,ax.data);
}
a& operator=(const a&ax)
{
if(&ax==this)
return *this;
data=new char[100];
memcpy(data,ax.data);
return *this;
}

};

Don’t believe what Scott said(just like first I read this)? Just try it, add a break points in a(const a& ax) and a& operator=(const a&ax) and see what happen.

How about C#?
No need to worry about that. In C#, all the class instance are reference(pointer), operator = just assigned the address. So you have no chance to make this mistake.

Oh, no, maybe still one chance. Strcut in C# is value type instead of reference type. What will happened to it?
public class Test
{
public struct TestStruct
{
public int s;
public TestStruct(int i)
{
Console.WriteLine("Constructor Called {1}",i);
s=i;
}
public ~TestStruct()
{
Console.WriteLine("Destructor Called");
Console.WriteLine("i={1}",i);
i++;
}
}

public static void Main(String[] s)
{
TestStruct t=new TestStruct(1);
TestStruct t1=t;
t=t1;
}
}
Will I get two different i printed?
Hehe, C# designer already see that, and won’t give u a chance to make mistake. No destructor is allowed for struct in C#. Destructor can be only used with class.

Item 13:

class a
{
private:
int* data;
int low;
int high;
public:
a(int l, int h):low(l),high(h),data(new int[high-low])
{
};
~a(){delete []data;}
void test()
{
for(int i=low;i<high;i++)
data[i-low]=i;
}
};
void main()
{
a* ax=new a(1,10);
ax->test();
delete ax;
}
anything wrong with it? It will crash randomly. Because here:
a(int l, int h):low(l),high(h),data(new int[high-low]);
The class member initialize order is not decided by the inistalize list, but by the order which member appear in the class. Which is to say: data(new int[high-low]); is actually initialized before low(l),high(h) is called.
So, when calling new int[high-low] the value of high and low is uninitilized, whether is crash or not totally depend on your luck.
How about C#?
Class member initialize list is not allowed in C#.
Then how to initilize const class member ? A constant value must be inialized when it declared.
Just like this:

public class a
{
private const int data=3;
a(int c)
{
// data=c;
}
public static void Main(string[] s)
{
a ax=new a(3);
}
};更多精彩文章及讨论,请光临枫下论坛 rolia.net
Report