Learning C++ if you already know C
Is it easy to migrate from C to C++?
Yes! C++ is nearly exactly a superset of Standard C95 (C90 and the 1995 Amendment 1). With very few exceptions, every valid C95 program is also a valid C++ program with the same meaning.
A great first step is to simply use C++ as “a better C,” which means that you can program in the C subset of C++ and find the experience better than in C because C++ provides extra type-checking and sometimes extra performance even for plain C code.
Of course, C++ also provides much more! Once you start compiling your existing C code as C++, you can just start selectively using C++ features tactically here and there as you’re comfortable – and start seeing benefits right away in each line of code.
What’s the difference between C++ and C?
C++ is a direct descendant of C95 (C90 plus an Amendment) that retains almost all of C95 as a subset. C++ provides stronger type checking than C and directly supports a wider range of programming styles than C. C++ is “a better C” in the sense that it supports the styles of programming done using C with better type checking and more notational support (without loss of efficiency). In the same sense, ANSI C90/C95 is a better C than K&R C. In addition, C++ supports data abstraction, object-oriented programming, and generic programming (see The C++ Programming Language; Appendix B discussing compatibility issues is available for downloading).
We have never seen a program that could be expressed better in C95 than in C++ (and we don’t think such a program could exist – every construct in C95 has an obvious C++ equivalent). However, there still exist a few environments where the support for C++ is so weak that there is an advantage to using C instead. There aren’t all that many of those left, though; see Stroustrup’s (incomplete) compilers list.
For a discussion of the design of C++ including a discussion of its relationship with C see The Design and Evolution of C++.
Please note that “C” in the paragraphs above refers to Classic C and C95 (C90 with an Amendment). C++ is not a descendant of C99; rather, both are derived from C95. C++11 adopted all of C99’s preprocessor extensions and library extensions, but not C99’s new language features, so language features like the restrict
keyword that were added in C99 are generally not part of ISO C++. Here is a description of the differences between C++98 and C99.
Is C a subset of C++?
In the strict mathematical sense, C isn’t a subset of C++. There are programs that are valid C but not valid C++ and even a few ways of writing code that has a different meaning in C and C++. However, C++ supports every programming technique supported by C95 (C90 plus an Amendment) and earlier. Every such C program can be written in essentially the same way in C++ with the same run-time and space efficiency. It is not uncommon to be able to convert tens of thousands of lines of ANSI C to C-style C++ in a few hours. Thus, C++ is as much a superset of C95 as C95 is a superset of K&R C and as much as ISO C++ is a superset of C++ as it existed in 1985.
Well written C tends to be legal C++ also. For example, every example in Kernighan & Ritchie: “The C Programming Language (2nd Edition)” is also a C++ program.
Examples of C/C++ compatibility problems:
int main()
{
double sq2 = sqrt(2); /* Not C++: call undeclared function */
int s = sizeof('a'); /* silent difference: 1 in C++ sizeof(int) in C */
}
Calling an undeclared function is poor style in C and illegal in C++. So is passing arguments to a function using a declaration that doesn’t list argument types:
void f(); /* argument types not mentioned */
void g()
{
f(2); /* poor style C. Not C++ */
}
In C, a void*
can be implicitly converted to any pointer type, and free-store allocation is typically done using malloc()
which has no way of checking if “enough” memory is requested:
void* malloc(size_t);
void f(int n)
{
int* p = malloc(n*sizeof(char)); /* not C++. In C++, allocate using `new' */
char c;
void* pv = &c;
int* pi = pv; /* implicit conversion of void* to int*. Not in C++ */
}
Note the potential alignment error caused by the implicit conversion of the void*
to an int*
. See the C++ alternative to void*
and malloc()
.
When converting from C to C++, beware that C++ has more keywords than C:
int class = 2; /* ok in C. Syntax error in C++ */
int virtual = 3; /* ok in C. Syntax error in C++ */
Except for a few examples such as the ones shown above (and listed in detail in the C++ standard and in Appendix B of The C++ Programming Language (3rd Edition)), C++ is a superset of C. (Appendix B is available for downloading).
Please note that “C” in the paragraphs above refers to Classic C and C95 (C90 with an Amendment). C++ is not a descendant of C99; rather, both are derived from C95. C++11 adopted all of C99’s preprocessor extensions and library extensions, but not C99’s new language features, so language features like the restrict
keyword that were added in C99 are generally not part of ISO C++. Here is a description of the differences between C++98 and C99.
Why use sort()
when we have good old qsort()
?
To a novice,
qsort(array,asize,sizeof(elem),elem_compare);
looks pretty weird, and is harder to understand than
sort(vec.begin(),vec.end());
To an expert, the fact that sort()
tends to be faster than qsort()
for the same elements and the same comparison criteria is often significant. Also, sort()
is generic, so that it can be used for any reasonable combination of container type, element type, and comparison criterion. For example:
struct Record {
string name;
// ...
};
struct name_compare { // compare Records using "name" as the key
bool operator()(const Record& a, const Record& b) const
{ return a.name<b.name; }
};
void f(vector<Record>& vs)
{
sort(vs.begin(), vs.end(), name_compare());
// ...
}
If you have a compiler supporting C++14, this gets even simpler:
struct Record {
string name;
// ...
};
void f(vector<Record>& vs)
{
sort(vs.begin(), vs.end(), [](auto &a, auto &b) { return a.name < b.name; });
// ...
}
In addition, most people appreciate that sort()
is type safe, that no casts are required to use it, and that they don’t have to write a compare()
function for standard types.
For a more detailed explanation, see Stroustrup’s paper “Learning C++ as a New language”, which can be downloaded from his publications list.
The primary reason that sort()
tends to outperform qsort()
is that the comparison inlines better.
Why must I use a cast to convert from void*
?
In C, you can implicitly convert a void*
to a T*
. This is unsafe. Consider:
#include<stdio.h>
int main()
{
char i = 0;
char j = 0;
char* p = &i;
void* q = p;
int* pp = q; /* unsafe, legal C, not C++ */
printf("%d %d\n",i,j);
*pp = -1; /* overwrite memory starting at &i */
printf("%d %d\n",i,j);
}
The effects of using a T*
that doesn’t point to a T
can be disastrous. Consequently, in C++, to get a T*
from a void*
you need an explicit cast. For example, to get the undesirable effects of the program above, you have to write:
int* pp = (int*)q;
or, using a new style cast to make the unchecked type conversion operation more visible:
int* pp = static_cast<int*>(q);
Casts are best avoided.
One of the most common uses of this unsafe conversion in C is to assign the result of malloc()
to a suitable pointer. For example:
int* p = malloc(sizeof(int));
In C++, use the typesafe new operator:
int* p = new int;
Incidentally, the new
operator offers additional advantages over malloc()
:
new
can’t accidentally allocate the wrong amount of memory,new
implicitly checks for memory exhaustion, andnew
provides for initialization
For example:
typedef std::complex<double> cmplx;
/* C style: */
cmplx* p = (cmplx*)malloc(sizeof(int)); /* error: wrong size */
/* forgot to test for p==0 */
if (*p == 7) { /* ... */ } /* oops: forgot to initialize *p */
// C++ style:
cmplx* q = new cmplx(1,2); // will throw bad_alloc if memory is exhausted
if (*q == 7) { /* ... */ }