24小时热门版块排行榜    

CyRhmU.jpeg
查看: 1753  |  回复: 5

cfem_nli

新虫 (初入文坛)

[交流] 【分享】在大的项目中为什么不用c++[也顺便测试一下你的c++基本水平]已有4人参与

语言之争历来有之,可谓是经久不息. 大一统的原则在这里并不适用. 对于科学计算, 很多人一直在为c++做宣传,为其翻身. 可是c++本身的缺点也是显而易见的,太过于复杂,一个优秀的c++程序员实在是不好找. 转载一篇很久以前看到的文章, 从电脑里翻出来,忘了出处了, 哪位要是知道麻烦广而告之. 看着篇文章,也顺便测试一下你的c++基本水平.

这是英文原文,后面附带了链接,可以下载.没有时间做适当的翻译,如果哪位仁兄实在有此需要的话,我再尽量找时间补上;要是有人能够翻译了,在这里先行谢过了.

Why I Dislike C++ For Large Projects   By Mark Roulo

My primary complaint against C++ is that the language is so complicated, and has enough booby-traps, that average and above-average programmers have a difficult time writing code without serious bugs.
In a large program with raw pointers, the quality of the program will largely be driven by the least talented members of the team. This makes it highly dangerous to select a language that requires all the programmers to be in, say, the top 25% of the talent pool. Given a team with 10 developers (and my projects typically have more like 40-50), this seems to be asking for lots of long term trouble.
Things become even more unstable when one considers that the average turnover of software developers in Silicon Valley (where I live and work) is something like 20-25% and that many large projects also use contractors. The total number of programmers on a project will be much higher than the average (or even the peak) number of developers on the project.
I have worked on a project with both C++ and Java components (roughly 50% each) that communicate via CORBA. Part of my job has been to interview candidates for both halves of the project over a period of several years. This has given me a fair amount of exposure to the C++ developer pool.
As part of my standard interview for C++ candidates I ask them to write me a small class with the intention of evaluating their command of the language. This also gives us a reasonable coding sample to discuss during the interview. I can ask about potential improvements, extensions and testing strategies.
Most of the candidates I interview have already made it to the top of the resume pool -- usually by claiming at least 3 years professional experience with C++. Since many resumes have this, the candidates tend to have some other plus: large systems experience, degree from a good school, personal recommendation, etc.
The candidates then must survive a phone screen interview whose job is to weed out candidates that can't, for example, describe any of their projects coherently.
My request is to:
Write a Named Point class with three members: two floating point values for the coordinates on an X-Y plane, and a name represented as a 'char *'. Assume that this class will be used for some sort of wargame or simulation program that treats the world as flat and that these named points will be used to represent things like cities, battlefields, etc.
A typical first try looks something like this:
    class NamedPoint
    {
    private:
        float x;
        float y;
        char *name;

    public:
        NamedPoint (float x, float y, char *name)
        {
            this->x    = x;
            this->y    = y;
            this->name = name;
        }

        float getX()    {return x;}
        float getY()    {return y;}
        char *getName() {return name;}

        void  setX(float x)       {this->x = x;}
        void  setY(float y)       {this->y = y;}
        void  setName(char *name) {this->name = name;}
    };
There are several problems with this code:
•        name has its encapsulation violated with the getName() method.
•        The code calling the constructor is responsible for managing the scope of the member variable 'name'. This code fragment shows the problem:
•                // Ignore for now a lot of awfulness in this function ...
•                // this should probably be a constructor in a sub-class
•                // of NamedPoint, 'cityName' and 'countryName' should be
•                // checked for NULL _and_ for length so that sprintf()
•                // doesn't overrun temp ...
•                //
•                // The point is that if NamedPoint doesn't *own* its own
•                // 'name' value, the clients are at risk of memory corruption.
•                //
•                NamedPoint makeCityCoordinate (float x, float y, char *cityName, char *countryName)
•                {
•                    char temp[80];
•                    sprintf (temp, "City: %s, Country: %s", cityName, countryName);
•       
•                    return NamedPoint (x, y, temp);
•                }
        
After I point out these problems, a typical fix is to modify NamedPoint to look like this (changes in bold):
    class NamedPoint
    {
    private:
        float x;
        float y;
        char *name;

    public:
        NamedPoint (float x, float y, char *name)
        {
            this->x    = x;
            this->y    = y;
            this->name = new char[strlen(name) + 1];
            strcpy (this->name, name);
        }

        float getX()          {return x;}
        float getY()          {return y;}
        const char *getName() {return name;}

        void  setX(float x)       {this->x = x;}
        void  setY(float y)       {this->y = y;}
        void  setName(char *name) {this->name = new char[strlen(name) + 1];
                                   strcpy (this->name, name);}
    };
This trades in one set of bugs for another. The new version has the following problems:
•        It doesn't have a destructor, so it leaks memory.
•        setName() doesn't delete name, so it leaks more memory if setName() is called.
•        strlen(NULL) and strcpy(NULL) are bad. Usually, a program will crash if this is attempted, so we really should check for NULL.
After I point this out, I usually get a third try that looks like this:
    class NamedPoint
    {
    private:
        float x;
        float y;
        char *name;

    public:
        NamedPoint (float x, float y, char *name)
        {
            this->x    = x;
            this->y    = y;
            if (name == NULL)
                this->name = NULL;
            else
            {
                this->name = new char[strlen(name) + 1];
                strcpy (this->name, name);
            }
        }

        ~NamedPoint ()
        {
            if (name != NULL)
                delete name;
        }

        float getX()          {return x;}
        float getY()          {return y;}
        const char *getName() {return name;}

        void  setX(float x)       {this->x = x;}
        void  setY(float y)       {this->y = y;}
        void  setName(char *name) {if (this->name != NULL)
                                       delete this->name;
                                   if (name == NULL)
                                       this->name = NULL;
                                   else
                                   {
                                       this->name = new char[strlen(name) + 1];
                                       strcpy (this->name, name);
                                   }}
    };
Things are slowly improving ... in the sense that the bugs are getting more and more subtle. It is also worth mentioning that over half of the candidates don't assign NULL to name if the input 'name' value is NULL, leaving the memory uninitialized. This really isn't a C++ issue. Failing to initialize pointers in C structs is equally bad. The new problems are:
•        NamedPoint allocates with 'new[]' but deletes with 'delete'. This may or may not work depending on the compiler. It seems to work fine for most current compilers, so I rarely comment on this. Still, it is incorrect.
•        Testing for NULL before calling delete is unnecessary (since 'delete 0' is defined to be harmless), but causes no damage other than slowing down the program slightly.
•        NamedPoint now trashes the heap if any NamedPoint objects are passed by value (like, for example, returning a NamedPoint object from a function). This is because the copy constructor that C++ gives us for free copies the 'name' pointer, but does not copy the contents. Now, calling the destructor on the first shared 'name' returns the memory to the heap (although the second copy will continue to use it, EVEN IF THE MEMORY GETS ALLOCATED TO SOME OTHER USE). Calling the destructor on the second shared 'name' probably corrupts the heap by deleting memory that was not, at that time, allocated (the second delete isn't required to corrupt the heap, but this is how most C++ heap managers work).
•        It has similar problems with the default assignment operator.
After pointing out the copy constructor and assignment operator problems, the fourth try usually looks like the code below. But not always. Sometimes I need to explain to the candidates what a copy constructor and assignment operator are. Some candidates have strange beliefs about when you need to implement them. One candidate, for example, believed that copy constructors were needed for classes above some size threshold, but not needed for classes below that size threshold. I'll emphasise that I'm interviewing candidates with several years C++ experience who have already passes a phone screen. In any event, typical attempt number four:
    class NamedPoint
    {
    private:
        float x;
        float y;
        char *name;

    public:
        NamedPoint (float x, float y, char *name)
        {
            this->x    = x;
            this->y    = y;
            if (name == NULL)
                this->name = NULL;
            else
            {
                this->name = new char[strlen(name) + 1];
                strcpy (this->name, name);
            }
        }

        ~NamedPoint ()
        {
            if (name != NULL)
                delete name;            
        }

        // NOTE: Most interviewees start with a signature
        //       like this:
        //           NamedPoint (NamedPoint copy)
        //
        NamedPoint (const NamedPoint & copy)
        {
            this->x = copy.x;
            this->y = copy.y;

            if (copy.name != NULL)
            {
                this->name = new char[strlen (copy.name) + 1];
                strcpy (this->name, copy.name);
            }
        }

        NamedPoint & operator=(const NamedPoint & copy)
        {
            this->x = copy.x;
            this->y = copy.y;
            if (this->name != NULL)
                delete this->name;

            if (copy.name != NULL)
            {
                this->name = new char[strlen (copy.name) + 1];
                strcpy (this->name, copy.name);
            }

            // Note that we haven't nulled out this->name, so
            // we can get a double-delete problem...
        }

        float getX()          {return x;}
        float getY()          {return y;}
        const char *getName() {return name;}

        void  setX(float x)       {this->x = x;}
        void  setY(float y)       {this->y = y;}
        void  setName(char *name) {if (this->name != NULL)
                                       delete this->name;
                                   if (name == NULL)
                                       this->name = NULL;
                                   else
                                   {
                                       this->name = new char[strlen(name) + 1];
                                       strcpy (this->name, name);
                                   }}
    };
This is almost correct! The big problems remaining are that the assignment operator doesn't check for assignment to itself, the copy constructor only partially copies if 'copy' has NULL for its name, and we still risk a double-delete via the assignment operator. If a program does try to assign one of these objects to itself, the object deletes its own 'name' value before attempting to copy it onto itself.
I usually stop here (assuming we get this far).
Conclusion
Empirically, I have found it very difficult to find even experienced C++ programmers capable of correctly writing a simple C++ class containing a pointer. Since pointers are, because of the C legacy, an important part of the language, this is a fatal flaw for large projects that need to work. Summarizing the mistakes in my fairly trivial problem:
•        Pointer assignment (a C legacy) makes it too easy to corrupt the stack and heap. The initial solution allows the stack to be accessed after it has gone out of scope. Corrected versions often allow for double deletes of heap allocated storage or accessing already deleted heap storage or both.
•        The default copy constructor and assignment operator are too often wrong. But you get them unless you explicitly take action. The language default being fatally wrong is a big problem.
•        delete and delete[] are similar, but possibly different.
•        NULL is legal for many pointer values, but the behavior tends to be undefined (delete being one nice exception). Since the NULL case is frequently overlooked, memory corruption again seems to be designed in to large systems.
One solution is to do a lot more stuff with things like STL string objects and generally try to hide the heap allocation. The auto_ptr<> and similar classes help here, too. But they only help. The fundamental problem still remains -- it is too easy to write subtly wrong code and the language is littered with booby-traps.
Larger programs encounter even more tricky problems. Scott Meyers has written two book on the subject of not getting killed by the C++ language. My point, though, is that most experienced C++ programmers I have interviewed can't get a simple class correct, even after multiple tries. Enough said. It makes me unwilling to risk a large project with the language.
http://dl.dropbox.com/u/1982439/CAE%20software%20development/Programming/Language%20arguments/Why%20I%20Dislike%20C%2B%2B%20For%20Large%20Projects.doc
回复此楼
明知不可为而为之,欢迎到我的Wordpress站点, http://cfem2nli.wordpress.com/
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

magic7004

金虫 (职业作家)

★ ★
小木虫(金币+0.5):给个红包,谢谢回帖交流
resonant(金币+1):欢迎参与:-) 2010-08-10 21:12:12
晕,这些都是C++的基础吧。指针初始化,拷贝构造函数、等号、析构函数。
流氓不可怕,可怕的是流氓有文化,有文化又BH的流氓无敌~~!
2楼2010-07-27 09:30:14
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

yalefield

金虫 (文坛精英)

老汉一枚

★ ★
小木虫(金币+0.5):给个红包,谢谢回帖交流
resonant(金币+1):欢迎参与:-) 2010-08-10 21:12:29
俺在小树林里的空地上练太极,总有几个小伙子也来练双节棍。
想一想,跟几个新出壳的共同开发一个工程,就是这个感觉。
他们总是用很复杂的、他们自己并不能真正掌握的东西,来给俺捣乱。
3楼2010-07-27 23:15:18
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

cfem_nli

新虫 (初入文坛)

★ ★
resonant(金币+2):维护费:-) 2010-08-10 21:12:48
引用回帖:
Originally posted by magic7004 at 2010-07-27 09:30:14:
晕,这些都是C++的基础吧。指针初始化,拷贝构造函数、等号、析构函数。

我想可能让你有所误会了。

首先呢,想说几句题外的话,历来的语言之争,其实乏味,大都是各领域的顶尖的一些人物,在那里宣传自己的种种好处,对我们来说,这样的争辩本身没有意思,但是呢,可以看到一些语言的基本的但是很深层次的东西,缺点或者是优点;

其次,如果要辩论,那么也是看应用背景,就是说我们要做什么样的应用开发,说白了,具体应用具体分析,没有大一统的语言

再三,稍微通用一些的层次上,比如说很多人包括我自己当时就问应该学什么语言好,如果说一定是要给点说法的话,那么,个人以为容易上手是一个首要的法则。就是说,对初学者,对一般层次的人,比如说本人,学的来,但是呢又不容易犯错。

关于我自己,其实非编程的科班出身,也不敢自认为高手,力学出身。做这样的思考,是因为当要作有限元软件开发的话,想比较比较那些语言在那些方面有优势,为什么现在的软件是现在这样的结构构成,等等问题。这样的背景,极有可能让我想问题的方式有偏颇,对语言的看法也来的不如程序高手那么直接和简洁,一语中的。


说了不少,回到先前的帖子上。英文原文的一些看法让我深以为然,两个原因,一个是:他涉及到了c++的构造函数,再一个是她说到的是项目开发的实际情况。对我己而言,不是编程特多,c++的构造函数很让我头疼,尤其是涉及到继承以后,构造函数很多类别,构造函数里的函数变量定义,初始化,exception handle,指针处理,不少的问题,让我感觉应付来有点头疼,觉着需要很多练习才行; 这是他说的第一个方面 。关于指针的话,我想,他没有提多少,但是估计让不少人构头疼的吧

第二个就是,实际做一个项目来说,就有很多的人来做。Top10 或者top20,在你的组里,是个好事,不过有一个差一些的也在做,那么可想而知你的麻烦事一定是源源不绝。很遗憾的是,那个造成这种局面的人往往意识不到那些潜在的错误。

至于说原帖那些都应该是c++的基础,或者说学过c++的人都该知道的,我不知道实际情况,我不知道怎么定义c++的基础内容。或许最好能做一个统计,看看大家常见的错误,或者令程序员们常常痛恨的错误,等等

但是并不是要来否定c++,linus说C++ Sucks,我不知道,没有他那么偏激。

欢迎有想的想法,欢迎指正。
明知不可为而为之,欢迎到我的Wordpress站点, http://cfem2nli.wordpress.com/
4楼2010-07-28 08:09:12
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

cfem_nli

新虫 (初入文坛)

顺便给出linus当时的关于c++ sucks争论的一个链接

首先呢,这个链接有点感觉是偏离主题了,只不过是我上面的帖子提到了这个,所以就权作为娱乐休闲,想看的朋友就关注一下   不想就这个作为讨论,实在是力不从心

我个人觉得之所以他那么偏激,可能一是他自己是一个c的fan,二是c当时是为系统开发而做的,可能就是适合linux kernel吧,印象中记得他说c++ 是在c上添枝加叶,尽做些不痛不痒的修改,说来说去,都不乏炒作之嫌

http://thread.gmane.org/gmane.comp.version-control.git/57918

why c++ sucks by linus
明知不可为而为之,欢迎到我的Wordpress站点, http://cfem2nli.wordpress.com/
5楼2010-07-28 08:29:13
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

tjyl

金虫 (正式写手)

★ ★
小木虫(金币+0.5):给个红包,谢谢回帖交流
resonant(金币+1):欢迎参与:-) 2010-08-10 21:12:56
牛人的脾气难免火爆了点。
事实就是在内核、驱动开发基本都用C的,其实这些地方不用C++没有啥好奇怪的。
引用回帖:
Originally posted by cfem_nli at 2010-07-28 08:29:13:
首先呢,这个链接有点感觉是偏离主题了,只不过是我上面的帖子提到了这个,所以就权作为娱乐休闲,想看的朋友就关注一下   不想就这个作为讨论,实在是力不从心

我个人觉得之所以他那么偏激,可能一是他自己是 ...

6楼2010-08-10 18:00:52
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖
相关版块跳转 我要订阅楼主 cfem_nli 的主题更新
普通表情 高级回复(可上传附件)
信息提示
请填处理意见