Why C++ is vastly superior to C

J

Juha Nieminen

Stefan Ram said:
the rise of objective C in recent
years due to products of a certain manufacturer

You forget that you can write your iPhone programs in Objective C++,
which means that you can write most of it in C++ and only what you must
in C (ie. those parts which need to interact with the Cocoa Touch
library). You can use full-fledged pure C++ libraries in your program.
(And in fact, if your program is OpenGL ES based rather than Cocoa Touch
based, you can skip writing any Objective C almost completely.)
 
J

Juha Nieminen

William Ahern said:
Proper ordering of resource mangement in C also gets you the majority of
these benefits, a result not of language features but of good programming
habits. Comparing poor resource management in C with good resource
management in C++ is comparing apples to oranges. If programmers were so
inept at tracking memory anyhow, they'd be best in a GCd environment. If
they feel the impulse to allocate dynamic strings randomly where ever it
suits them, then they're begging for leaks, and really should be using a GCd
language, whether in C or C++. If they properly order allocation of these
resources, then the gap between C and C++ becomes quite small; maybe not
small enough, but perhaps for other considerations to move to the forefront.

You can achieve something similar to C++'s RAII in C within one single
function if you carefully follow certain coding conventions (which, as
said, are completely unnecessary in C++). The problem is that it's not
at all uncommon that resource handling spans beyond the scope of one
single function.

In C++ you mostly don't have to worry. Basically, if you never use
'new' nor pointers in your own code, you are usually on safe grounds, even
if you are dealing with dynamically allocated memory, and even if you are
passing objects handling this memory around at will.

The simplest example is a function returning resources that it allocated
itself. This immediately breaks encapsulation in C, as it transfers the
burden of freeing that resource to the caller. Of course you can do this
in C++ as well, but that would generally be a mistake, as the language
offers the tools to retain the encapsulation even when the resource
allocation outlives the function where it was done. Just as a simple
example:

std::string gimmeTheString()
{
std::string line;
std::getline(std::cin, line);
// Or any other dynamic string building routine you can imagine.

return line;
}

Encapsulation is preserved because the calling code doesn't need to
worry about how the dynamically allocated string will be deleted. When the
return value goes out of scope, it will be done automatically.

The lack of RAII in C causes resource management responsibility
trasferral, and thus breaking encapsulation. The above function equivalent
in C would require the calling code to be careful about the returned
string.

And that was just one of the simplest cases where resource allocation
can outlive the scope where the allocation was done. It can get
significantly more complex than that, with deeply nested shared containers
and other such situations which do happen in practice. The set of
programming conventions you need for safe code in C++ is significantly
smaller than the set of programming conventions necessary in C. The more
complicated the situation, the larger the difference.

That is one of the main reasons why I would consider it nightmarish if I
had to develop a large and complex project in C.

RAII becomes even more important in generic programming. If you want a
function or class to support more than one data type (regardless of
whether only one such type will be used at a time in the entire program,
or whether the same code could be used with several different types within
the same program), automatic resource management becomes quite essential.
Basic (non-pointer) types don't need any resource management because they
can be handled by-value. Structs and classes may need them because they
may be encapsulating allocated resources within them. However, in C++ you
don't have to take this into account in your generic code, which is what
makes it so great.
[I'll refrain from commenting on the obscene nested containers example as
I'm sure it made you cringe as well.]

It was just a one-liner demonstration. It was not intended to be an
actual real-life example.

In real code you can, however, end up with deeply nested containers,
even if more indirectly. Typically you will have a class that has data
containers as members, and instances of this class may be used in a
container (which itself might be a member of another class, and so on).
This isn't even a rare happenstance.
 
J

Juha Nieminen

Stefan Ram said:
RAII helps to release resources even in the case of exceptions.
But C does not have exceptions. So RAII is not needed for this.

Besides what others responded, I'll like to comment that I have not
previously heard the argument that "RAII is only needed because of
exceptions". (Ok, you are not exactly saying that, but it sounds like it.)

RAII would certainly be exactly as useful even without exceptions. In
fact, in many compilers you can compile a C++ program without any support
for exceptions by using a compiler option (naturally it requires that your
code has no explicit throws). That wouldn't diminish the importance of
RAII in the least bit.
 
R

Rui Maciel

Stefan said:
The curves, however, agree with what I observe elsewhere:

The rise of Ruby in 2006, the rise of objective C in recent
years due to products of a certain manufacturer, a gradual
decline of Perl and so on. Certain, the data /is/ noisy,
but it cannot be all noise. I would apply some moving
average filter to the curves to make them more smooth.

Here is another view from another source:

http://www.google.com/trends?q=C+programming,C+++programming

. What do these curves mean? Random noise plus a systematic bias?

If you take the time to learn how google trends works[1] you will realize
that it only measures google search queries and the frequency that the
topic is covered in google news stories. So, knowing that, when you run
some tests you quickly find out that the results are meaningless in terms
of any attempt to evaluate market share[2].


Rui Maciel


[1] http://www.google.com/intl/en/trends/about.html
[2]
http://www.google.com/trends?q=Java,+C++,+C,+ruby,+C#&ctab=0&geo=all&date=all&sort=0
 
K

Keith H Duggar

I disagree; generic programming is just as important as object oriented
programming.

Agreed. Generic programming is at least as important and much more
unique to C++ (relative to C) than OOP. Although, I would say that
the C++ automatic constructor/destructor paradigm (which was a C++
innovation as far as I know?) is also extremely valuable. Function
overloading was also a major advance over traditional C.

KHD
 
S

Stefan Ram

gwowen said:
example given in the article, if you're making a linked list of
dynamically allocated object, and the system runs out of memory on n-
th object then you need to make sure the previous n-1 objects are
correctly deallocated at the error. In C, that's definitely possible,
but its quite unpleasant - not least because your unwinding code will
likely need to be close to the creation code, which makes your code
harder to read.

I think that the unwinding code /should/ be close to the
creation code, /because/ that will make the whole code
easier to read. So, in C,

/* Enlarge a list by appending n new entries to it, all of which just
contain a dummy null pointer for the purpose of this example.

When it is detected that a new entry cannot be append anymore
(for example due to lack of memory), the entries appended so far
will be removed and the function will return a nonzero value. */

int enlarge_list( list_t * const list, int const n /* precond: i > -1 */ )
{ int result = 1; int i; /* result == 0 means success */
for( i = 0; i < n; ++i ) /* append n new entries to the list */
if( list_push( list, 0 )) /* appends a null pointer to the list */
break; /* break on error to push a new entry */
if( i < n )list_pop( list, i ); /* clean up by removing all appended */
else result = 0; /* or else set the value for success */
return error; }

(untested.)
 
S

Stefan Ram

Juha Nieminen said:
std::string gimmeTheString()
{
std::string line;
std::getline(std::cin, line);
// Or any other dynamic string building routine you can imagine.

return line;
}

Encapsulation is preserved because the calling code doesn't need to
worry about how the dynamically allocated string will be deleted. When the
return value goes out of scope, it will be done automatically.

This seems to be due to the string being passed by value,
so that it will be copied?

But wouldn't it be more efficient to pass such a possibly
large object by a pointer or reference, which also saves
time for allocation and deallocation? And when doing so,
will it still be true that one does not have to worry?
 
P

Paul Brettschneider

Stefan said:
This seems to be due to the string being passed by value,
so that it will be copied?

But wouldn't it be more efficient to pass such a possibly
large object by a pointer or reference, which also saves
time for allocation and deallocation? And when doing so,
will it still be true that one does not have to worry?

A good std::string implementation _will_ be in terms of a reference/pointer
for large strings. That's the fantastic thing about it: you don't need to
bother about those internal details like refcounting. Also return of large
objects can be implemented with reference semantics.

I suggest you get familiar with the C++ programming language by using it in
practice. It seems you do not yet 'get' it. You will see that once you
master RAII you will never want to go back to old-fashioned C.
 
N

none

This seems to be due to the string being passed by value,
so that it will be copied?

No it won't. Not with any decent compiler.
But wouldn't it be more efficient to pass such a possibly
large object by a pointer or reference, which also saves
time for allocation and deallocation? And when doing so,
will it still be true that one does not have to worry?

No, it may even lead to less efficient code in fact. You need to do a
bit of basic research on the subject.

Measure first, then if you can demonstrate that the natural simple and
elegant way to write the code is too slow, then jump through
hoops. don't do premature optimization... especially when what you
propose could well turn out to be premature pessimization.

Yannick
 
K

Keith H Duggar

i.e. a reference-counted string? No, there was only one commercial
implementation that did that, and now it's explicitly prohibited
because of the complications that it introduces in multi-threaded code.

http://www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf

Reference counted strings (or more generally flyweight patterns
of various kinds) are very useful tools. Just because they don't
interact well with a badly broken model of concurrency is not a
reason to abandon them entirely. You simply don't use said model
of concurrency (yes, believe it or not their are other superior,
for most uses, models) or don't use flyweights together with it.

KHD
 
J

Juha Nieminen

Keith H Duggar said:
Function
overloading was also a major advance over traditional C.

Function overloading is especially important in generic programming.
In fact, many of the things that C++ adds compared to C are valuable
(if not even mandatory) for generic programming.
 
K

Keith H Duggar

http://www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf

Reference counted strings (or more generally flyweight patterns
of various kinds) are very useful tools. Just because they don't
interact well with a badly broken model of concurrency is not a
reason to abandon them entirely. You simply don't use said model
of concurrency (yes, believe it or not their are other superior,
for most uses, models) or don't use flyweights together with it.

Oh, I see that you were probably referring specifically and only
to std::string? If so, then my comments probably don't apply. It
certainly might be a good idea for std::string to be as thread
compatible as possible (given the industries unhealthy addiction
to threads).

KHD
 
J

Juha Nieminen

Stefan Ram said:
This seems to be due to the string being passed by value,
so that it will be copied?

That's not the point. It doesn't matter if the string is returned by
value, or via a reference/pointer given to the function as parameter:
Encapsulation is still maintained and freeing the resource is not
transferred to the calling code.
But wouldn't it be more efficient to pass such a possibly
large object by a pointer or reference, which also saves
time for allocation and deallocation? And when doing so,
will it still be true that one does not have to worry?

In this particular case most compilers will use return value optimization,
which means that the function constructs the return value right on the
caller's stack rather than on its own. Thus the copying will be averted.
(A copy might be triggered in the calling code, depending on the situation
and how the function is being called, but that's another issue.)

If large containers are being passed by value a lot in the program,
you could implement a copy-on-write wrapper around them. The nice thing
is that the exact method by which the copying is done is inconsequential
in the code that uses it.
 
P

Paul Brettschneider

Pete said:
i.e. a reference-counted string? No, there was only one commercial
implementation that did that, and now it's explicitly prohibited
because of the complications that it introduces in multi-threaded code.

Or a unique_ptr kind of thing with move semantics. In any case I would not
expect a good implementation to copy a long string just for returning it
from a function. That would be for me a much more serious drawback than
subpar performance under multi-threaded conditions.
 
G

gwowen

I think that the unwinding code /should/ be close to the
creation code, /because/ that will make the whole code
easier to read.

So, enlarge_list returns an error status .. and its caller checks it
and ... then what?

Either the caller must then have its *own* error unwinding code, near
every call site, and it *must* have a return type suitable for
returning the error status of every function it calls, and ... its
elephants all the way down ... and for list_pop() to work, every
container must know exactly how to deallocate its own contained type.

And even then, the list_pop() function needs to know how to safely
deallocate the object types stored in list_t (evening assuming you can
usefully define a single list_t type). That's fine if its int's, less
fine for almost anything else, so say goodbye to lists of list_t's,
etc, etc...

Yes, you *can* fake all these things in C. You can do them in machine
code too. That doesn't make it a good idea.
 
S

Stefan Ram

gwowen said:
So, enlarge_list returns an error status .. and its caller checks it
and ... then what?

There can not be a general answer to this. It is the callers
decision.
Either the caller must then have its *own* error unwinding code, near
every call site, and it *must* have a return type suitable for
returning the error status of every function it calls,

Well, the details depend on what the caller is intended to do.

In the code below, for example, return values of functions
called are coalesced into an own return value. For example,
to read, sort and print a list using

http://mij.oltrelinux.com/devel/simclist/

(this code was not completely tested, it still might
contain bugs, but one can get the idea):

#include <stdio.h> /* scanf, printf */
#include <stdlib.h> /* EXIT_SUCCESS */
#include "simclist-1.4.3/simclist.h" /* list_... */

int appendvalue( list_t * const list, int *looping )
{ int value;
if( 1 != scanf( "%d", &value ))return 1;
if( value >= 0 )
{ if( list_append( list, &value )< 0 )return 2; }
else *looping = 0;
return 0; }

int read( list_t * const list )
{ int looping = 1; while( looping )
{ if( appendvalue( list, &looping ))return 1; }
return 0; }

int sort( list_t * const list )
{ if( list_attributes_comparator( list, list_comparator_int32_t ))return 1;
else if( list_sort( list, -1 ))return 2;
else return 0; }

int print( list_t * const list )
{ if( list_iterator_start( list )<= 0 )
return 1;
{ while( list_iterator_hasnext( list ))
{ void * next = list_iterator_next( list );
if( next )
{ if( printf( "%d\n", *( int * )next )< 0 )return 2; }
else break; }
if( !list_iterator_stop( list ))return 3; }
return 0; }

int main2( list_t * const list )
{ if( !list_attributes_copy( list, list_meter_int32_t, 1 ))
{ if( read( list ))return 1;
else if( sort( list ))return 2;
{ printf("Sorted values:\n");
if( print( list ))return 3; }}
return 0; }

int main1( list_t * const list )
{ int result;
if( list_init( list ))result = 1;
else
{ if( main2( list ))result = 2;
list_destroy( list ); }
return result; }

int main( void )
{ list_t list;
return main1( &list )? EXIT_FAILURE : EXIT_SUCCESS; }
 
B

Balog Pal

Pete Becker said:
i.e. a reference-counted string? No, there was only one commercial
implementation that did that, and now it's explicitly prohibited because
of the complications that it introduces in multi-threaded code.

But that applies only to std::string, and caused by its broken public
interface. A normal string class that is designed to be a stirng, and tuned
for natural use cases of a string -- instead of ignoring those, but try to
play a generic container is free to have COW or any other tech.
 
B

Balog Pal

Keith H Duggar said:
Reference counted strings (or more generally flyweight patterns
of various kinds) are very useful tools. Just because they don't
interact well with a badly broken model of concurrency is not a

IIRC the problem was more tied to ignoring 'don't leak your internals'
principle (especially in innocent-looking functions like operator[] const)
than concurrency itself.
 
I

Isaac Gouy

  According to

http://shootout.alioth.debian.org/u32/which-programming-languages-are...

  C++ is not much faster than C (the median value actually is
  a little bit slower than C if I understand the table right),
-snip-


When you're just comparing 2 language implementations the Help page
suggests that you compare them side-by-side

http://shootout.alioth.debian.org/u32/cpp.php

http://shootout.alioth.debian.org/u64/c.php


Also

http://shootout.alioth.debian.org/u64q/c.php

http://shootout.alioth.debian.org/u32q/c.php
 
J

Juha Nieminen

Stefan Ram said:

For curiosity I examined how that implementation handles the list
elements.

Overhead (each element of the list makes two allocations rather than one,
one allocation for the node and another for the user data; also comparison
is done via indirect function calls via function pointers, making them
significantly slower than direct element comparisons) and lack of
type safety (everything is handled through void pointers, making it
impossible for the compiler to catch accidental mistakes in types) is to
be expected from a "generic" C implementation.

(I have always found it quite ironic that C hackers accuse C++ of
introducing needless overhead, when it's usually the exact opposite,
with C having the needless overhead when compared to the equivalent
C++ implementation, especially when talking about generic containers.)

The most striking problem, however, is summarized by these lines of
code in the library:

size_t datalen = l->attrs.meter(data);
lent->data = (struct list_entry_s *)malloc(datalen);
memcpy(lent->data, data, datalen);

and:

if (s->data != NULL) free(s->data);

The library assumes that it can just make a raw bitwise copy of the
element when it needs to create a new node, and simply free the element
when it removes a node. Well, guess if you can make eg. a list of lists
with this (or, for that matter, have as list element a struct that manages
some resource which needs to be release when the struct instantiation is
destroyed).

Sure, you could add yet another pair of function pointers to the
list_attributes_s struct (ironically adding yet more overhead that a C++
implementation would not require) for copying and destroying the list
elements. Or the coder who is using the list could make sure that the
elements are being finalized before they are removed from the list
(something that is easily error-prone in complicated programs). However,
I think this is a good representation of the natural kind of thinking in C.
Even in code that's supposed to have high encapsulation, encapsulation is
still being broken.

And btw, there's an error in the webpage. It says:

"sorting is always O(n logn), without worst case"

Seeing that it uses quicksort, that's just not true. (Randomizing the
pivot does not make the worst case scenario any better than with vanilla
quicksort.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,015
Latest member
AmbrosePal

Latest Threads

Top