C++ constructs replacing C constructs [closed]
Asked Answered
C

20

17

After discussing with a newly arrived developer in my team, I realized that there are still, in C++, habits of using C constructs because they are supposed to be better (i.e. faster, leaner, prettier, pick your reason).

What are the examples worth sharing, showing a C constructs, compared to the similar C++ construct?

For each example, I need to read the reasons the C++ construct is as good as or even better the original C construct. The aim is to offer alternatives to some C constructs that are considered somewhat dangerous/unsafe in C++ code (C++0x valid only answers are accepted as long as clearly marked as C++0x only).

I'll post below an answer (struct inline initialization) as an example.

Note 1: Please, one answer per case. If you have multiple cases, please post multiple answers

Note 2: This is not a C question. Do not add the "C" tag to this question. This is not supposed to become a fight between C++ and C. Only the study of some constructs of the C subset of C++, and their alternative in other C++ "toolkits"

Note 3: This is not a C-bashing question. I want reasons. Boasting, bashing, and unproven comparisons will be downmodded. Mentioning C++ features without a C equivalent could be considered out of topic: I want the put side by side a C feature against a C++ feature.

Circumlunar answered 22/10, 2008 at 17:48 Comment(11)
Why not add the C tag? This question, while not directly about C, is certainly relevant to people interested in C.Cataplasm
I agree with Adam about the C tag.Rase
My initial reasons were that I did not want a C++ vs C bashing fest. This is not an attempt to "convert", and this is not an attempt to boast one language against the other. I just did not want to have a "fight"... Still, you're perhaps right.Circumlunar
If there's no attempt to convert, why does the C++ construct a priori have to be 'as good as or even better'?Know
Because when C is better and not dangerous, it remains a good alternative for the developer. So, I'm only interested in the cases when C is not better.Circumlunar
I added the "c" tag, after the comments of Adam and Mike. Thanks for the input.Circumlunar
Do we have a 'propaganda' tag? I won't be contributing to this thread any more - the game is rigged.Know
@fizzer.myopenid.com, I don't think he's saying that C++ is always better. I think he's looking for examples of cases where C++ is better. I know more than one developer who still likes C better than C++ because C++ can be more tricky to debug due to operator overloading and issues of that sort.Institutive
@Onorio: I guess there was a misunderstanding: Despite what his comment (probably written in haste) could let believe, Fizzer contributed positively to the thread and showed a good understanding of C++. My own knowledge of C++ increased because of the discussion we had.Circumlunar
@Onorio: [...] But you did plainly described my viewpoint, probably better than I did in my posts. Thanks.Circumlunar
Marked as poll because multiple answers are expected, not one.Bumblebee
S
34

RAII and all the ensuing glory vs. manual resource acquisition/release

In C:

Resource r;
r = Acquire(...);

... Code that uses r ...

Release(r);

where as examples, Resource could be a pointer to memory and Acquire/Release will allocate/free that memory, or it could be an open file descriptor where Acquire/Release will open/close that file.

This presents a number of problems:

  1. You might forget to call Release
  2. No information about the data flow for r is conveyed by the code. If r is acquired and released within the same scope, the code does not self-document this.
  3. During the time between Resource r and r.Acquire(...), r is actually accessible, despite being uninitialized. This is a source of bugs.

Applying the RAII (Resource Acquisition Is Initialization) methodology, in C++ we obtain

class ResourceRAII
{
  Resource rawResource;

  public:
  ResourceRAII(...) {rawResource = Acquire(...);}
  ~ResourceRAII() {Release(rawResource);}

  // Functions for manipulating the resource
};

...

{
  ResourceRAII r(...);

  ... Code that uses r ...
}

The C++ version will ensure you do not forget to release the resource (if you do, you have a memory leak, which is more easily detected by debugging tools). It forces the programmer to be explicit about how the resource's data flow (ie: if it only exists during a function's scope, this would be made clear by a construction of ResourceRAII on the stack). There is no point during between the creation of the resource object and its destruction where the resource is invalid.

Its also exception safe!

Stephens answered 22/10, 2008 at 19:4 Comment(2)
I believe in your example, you need to adjust the constructor and destructor to be named ResourceRAII() and ~ResourceRAII(), as that is the name of the class.Pantin
+1, RAII is the best C++ invention ever. I did not even found a single case where it was "over-used" (like e.g. a singleton pattern). Its just the best thing since sliced bread, and if would've been invented earlier, maybe nobody would've had to invent garbage collection ...Basophil
D
27

Macros vs. inline templates

C style:

#define max(x,y) (x) > (y) ? (x) : (y)

C++ style

inline template<typename T>
const T& max(const T& x, const T& y)
{
   return x > y ? x : y;
}

Reason to prefer C++ approach:

  • Type safety -- Enforces that arguments must be of same type
  • Syntax errors in definition of max will point to the correct place, rather than where you call the macro
  • Can debug into the function
Destination answered 22/10, 2008 at 18:31 Comment(11)
In addition, the C++ version doesn't evaluate arguments twice. Consider max(++i, ++j): the C version will increment either i or j twice rather than once.Herbst
Perhaps a better version would be two "max" functions. One waiting for const reference parameters and returning the const reference of the max, the other waiting for non-const reference parameters, and returning the non-const reference of the max... But this is out topic ^_^ ... +1Circumlunar
Max really isn't that simple, you really should read this ddj.com/cpp/184403774Surefooted
Right -- I remember reading that when it came out -- my version also makes a copy, which is probably undesirable and I will edit to fix.Destination
The C version is wrong, dangerous, or both. I'll add the parentheses necessary to make it safe(r).Waterage
You've still got the C version wrong. parens around the entire expansion are needed as well.Mantissa
The C version may be dangerous, but I still use it in preference to "80 lines of template source code (plus the Loki library) which won't compile with any currently available compiler". (summary of motti's link)Mantissa
@Roddy: JohnMcG's solution, completed with the non-const version, is more than enough for most uses. I've yet to be bothered by std::min and std::max offered by the STL (use the one line "#include <algorithm>"), while a "#define max" macro already trashed a perfectly namespaced/encapsulated code.Circumlunar
Of coutse, that the macro version is complicated to get right is part of the problem with it.Destination
Not withstanding that Macros have a 'scope' from #define to #undef or the end of the file. template<>s are scoped by namespaces, classes, functions, and blocks, just as other function and class names are.Pantin
It took me a few tries, but i hope it's fine now: codepad.org/R6i0te8h . tries to work with const/nonconst parameters, and with them having different types (finds their common type by applying of operator ?: ) no loki or whatsoever needed. but maybe there is a bug i've overseen?Allan
T
18

Dynamic arrays vs. STL containers

C-style:

int **foo = new int*[n];
for (int x = 0; x < n; ++x) foo[x] = new int[m];
// (...)
for (int x = 0; x < n; ++x) delete[] foo[x];
delete[] foo;

C++-style:

std::vector< std::vector<int> > foo(n, std::vector<int>(m));
// (...)

Why STL containers are better:

  • They're resizeable, arrays have fixed size
  • They're exception safe - if unhandled exception occurs in (...) part, then array memory may leak - container is created on stack, so it will be destroyed properly during unwind
  • They implement bound checking, e.g. vector::at() (getting out of bounds on array will most likely generate an Access Violation and terminate the program)
  • They're easier to use, e.g. vector::clear() vs. manually clearing the array
  • They hide memory management details, making code more readable
Tawny answered 22/10, 2008 at 19:27 Comment(3)
I guess there is no need for a 2D array. A 2D array for C++ could be broken (i.e. one line could be 5 items long, and the other could 7). Whereas a <code>char a[25][6]</code> is always Ok and fast. The same example with a 1D array vs. a vector would be better. Until we have N-dimension arrays in C++Circumlunar
Note, too, that the access through vector::operator[] is as fast as the one one a C array through pointer arithmetics or [] use.Circumlunar
P.S.: When I write "A 2D array for C++ could be broken", I write about an exposed vector of vector. A private vector of vector in a "Matrix" class would not be victim of this problem thanks to encapsulation. But this goes beyond the topic, I guess.Circumlunar
I
17

#define vs. const

I keep seeing code like this from developers who have coded C for a long time:

#define MYBUFSIZE 256

.  .  . 

char somestring[MYBUFSIZE];

etc. etc.

In C++ this would be better as:

const int MYBUFSIZE = 256;

char somestring[MYBUFSIZE];

Of course, better yet would be for a developer to use std::string instead of a char array but that's a separate issue.

The problems with C macros are legion--no type checking being the major issue in this case.

From what I've seen, this seems to be an extremely hard habit for C programmers converting to C++ to break.

Institutive answered 22/10, 2008 at 19:32 Comment(7)
This used to be awkward because compilers/linkers wouldn't let you place "const int X=100" in the .h file - you needed "extern const int X" in a header, and "const int x=100;" in a .c file, which was painful. Seems to work OK now, though.Mantissa
@Mantissa : Today, this is ok for const expressions (I guess they will be inlined away)Circumlunar
I've actually grown to like using enums for constants - works well in both C and C++. The only drawback is that you don't get to force a particular type, but that's rarely a concern.Rase
One big problem with macros like this is that they are global. What if two unrelated libraries define "BUFSIZE"?Pertain
@Pertain : To be more clear, Macros have a 'scope' from #define to #undef or the end of the file. consts are scoped by namespaces, classes, functions, and blocks, just as variables.Pantin
I like to use "static const int x = 100;" in the header files. Each translation unit will then technically have it's own copy of the constant, but as paercebal points out, you can expect it to be inlined away.Genia
In the Microsoft debugger, putting your cursor over the const int defined label will display the value, but not for labels using #define. This is enough reason for me to prefer it.Uniliteral
C
14

Default parameters:

C:

void AddUser(LPCSTR lpcstrName, int iAge, const char *lpcstrAddress);
void AddUserByNameOnly(LPCSTR lpcstrName)
  {
  AddUser(lpcstrName, -1,NULL);
  }

C++ replacement/equivalent:

void User::Add(LPCSTR lpcstrName, int iAge=-1, const char *lpcstrAddress=NULL);

Why it's an improvement:

Allows programmer to write express the function of the program in fewer lines of source code and in a more compact form. Also permits default values for unused parameters to be expressed closest to where they are actually used. For the caller, simplifies the interface to the class/struct.

Convexoconvex answered 22/10, 2008 at 18:50 Comment(5)
I don't really like that use of overloaded functions. Yes, they all add info, but I don't think they should be one function that's overloaded.Chamberlin
I reformatted the post to have the code pretty-printed. Please Steve, could you edit your post to break it into two posts, and explain the pros/cons of each solution? (And change, perhaps, the LPCSTR into const char * for the non-Windows people?). I'm waiting for that to add a +1 to each post.Circumlunar
@Thomas: I agree, even if it still depends on the context. While the example can be discussed, their comparison with the C code is what gives them their value in the current topic. I'm surprised no one mentionned the "abs" and "fabs" of C against a possible cpp::abs function.Circumlunar
@paercebal. Thanks for the edit and the suggestions. I have done as you requested.Convexoconvex
Default parameters are very useful, but one must be careful with them. I've seen them pretty badly abused ("magic" default values that drastically change the function's behaviour, for instance.)Blandina
W
13

C's qsort function versus C++' sort function template. The latter offers type safety through templates which have obvious and less obvious consequences:

  • Type safety makes the code less error-prone.
  • The interface of sort is slightly easier (no need to specify the size of the elements).
  • The compiler knows the type of the comparer function. If, instead of a function pointer, the user passes a function object, sort will perform faster than qsort because inlining the comparison becomes trivial. This isn't the case with function pointers that are necessary in the C version.

The following example demonstrates the usage of qsort versus sort on a C-style array of int.

int pint_less_than(void const* pa, void const* pb) {
    return *static_cast<int const*>(pa) - *static_cast<int const*>(pb);
}

struct greater_than {
    bool operator ()(int a, int b) {
        return a > b;
    }
};

template <std::size_t Size>
void print(int (&arr)[Size]) {
    std::copy(arr, arr + Size, std::ostream_iterator<int>(std::cout, " "));
    std::cout << std::endl;
}

int main() {
    std::size_t const size = 5;
    int values[] = { 4, 3, 6, 8, 2 };

    { // qsort
        int arr[size];
        std::copy(values, values + size, arr);
        std::qsort(arr, size, sizeof(int), &pint_less_than);
        print(arr);
    }

    { // sort
        int arr[size];
        std::copy(values, values + size, arr);
        std::sort(arr, arr + size);
        print(arr);
    }

    { // sort with custom comparer
        int arr[size];
        std::copy(values, values + size, arr);
        std::sort(arr, arr + size, greater_than());
        print(arr);
    }
}
Waterage answered 22/10, 2008 at 20:9 Comment(3)
Please, could you add code as example: One use of qsort, and one use of std::sort?Circumlunar
Note that Bjarne Stroustrup confirms Konrad's answer: research.att.com/~bs/bs_faq2.html#sortCircumlunar
I believe I read somewhere C got better at inlining call to function through function pointers, but I have still to find that article. Anyway, the point is more than valid.Circumlunar
C
8

struct inline initialization vs. inline constructors

Sometimes, we need in C++ a simple aggregation of data. The data being somewhat independant, protecting it through encapsulation would not be worth the effort.

// C-like code in C++
struct CRect
{
   int x ;
   int y ;
} ;

void doSomething()
{
   CRect r0 ;               // uninitialized
   CRect r1 = { 25, 40 } ;  // vulnerable to some silent struct reordering,
                            // or adding a parameter
}

; I see three problems with the code above:

  • if the object is not specifically initialized, it won't be at initialized all
  • if we echange x or y (for whatever reason), the default C initialization in doSomething() will now be wrong
  • if we add a z member, and liked it to be "zero" by default, we would still need to change every inline initializing

The code below will have the constructors inlined (if really useful), and thus, will have a zero cost (as the C code above):

// C++
struct CRect
{
   CRect() : x(0), y(0) {} ;
   CRect(int X, int Y) : x(X), y(Y) {} ;
   int x ;
   int y ;
} ;

void doSomething()
{
   CRect r0 ;
   CRect r1(25, 40) ;
}

(The bonus is that we could add a operator== methods, but this bonus is out of topic, and so worth mentioning but not worth as an answer.)

Edit: C99 has named initialized

Adam Rosenfield made an interesting comment I find very interesting:

C99 allows named initializers: CRect r = { .x = 25, .y = 40 }

This won't compile in C++. I guess this should be added to C++, if only for C-compatibiliy. Anyway, in C, it alleviates the problem mentioned in this answer.

Circumlunar answered 22/10, 2008 at 17:49 Comment(9)
C99 allows named initializers: CRect r = { .x = 25, .y = 40 };Cataplasm
Interesting point. This won't compile in C++ g++ ("error: expected primary-expression before ‘.’ token"), so this does not invalidate this whole post, but still a very good point worth knowing.Circumlunar
Nope, it doesn't compile with g++, but if you're compiling pure C code, it works with gcc. I've tested it with gcc 3.4.4, but I'm sure it works in plenty of other versions of gcc as well.Cataplasm
I don't see why inline initialisation is "vulnerable" to adding or re-ordeing members, but calling a constructor apparently isn't "vulnerable" to adding or re-ordering parameters. Either an interface is fixed or it isn't, and that applies to a struct as much as it does to a constructor.Dissuasive
Actually, I'll give you "adding a parameter", since if you add a parameter to the constructor, the code will stop compiling, whereas if you add a member to a struct then the leftovers are silently 0-inited. But they're equally vulnerable to re-ordering.Dissuasive
@onebyone: Take the two CRect structs, compile them, and see the result. Then invert the int x/int y declaration inside the struct. You'll see the C CRect is inverted, while the C++ CRect has still the correct result. [...]Circumlunar
[...] The "interface" of the C++ CRect is the constructor (as long as the constructor is unchanged, everything's ok), while the interface of the C CRect is the struct itself (the moment it's changed, it won't work). The zero-cost inline C++ indirection given by the constructor changes everything.Circumlunar
I agree with this post, but wanted to point out that adding a constructor to a struct will make it no longer a POD type. This is not the case in C++0xWeiweibel
@michalmocny: You're right, but the "POD" is a non issue on C++ (i.e. who cares?) unless you're playing with shared memory between processes, malloc-allocated memory or other pure-C-features. In this post, I was only mentionning C-style constructs against C++ constructs in pure C++ compiled code.Circumlunar
P
7

iostream vs stdio.h

In C:

#include <stdio.h>

int main()
{
    int num = 42;

    printf("%s%d%c", "Hello World\n", num, '\n');

    return 0;
}

The format string is parsed at runtime which means it is not type safe.

in C++:

#include <iostream>

int main()
{
    int num = 42;

    std::cout << "Hello World\n" << num << '\n';
}

The data types are known at compile time, and there's also less to type because there is not need for a format string.

Procurer answered 22/10, 2008 at 19:22 Comment(10)
Have you ever profiled stdio vs. iostream? I think you'll be surprised to find that stdio is significantly faster than iostream.Cataplasm
I agree. While this is interesting (sprintf is easy to abuse), iostreams are slow enough (i.e. 5 times stdio equivalent if my memory serves) to have Herb Sutter write an article on it. [...]Circumlunar
[...] Thus, this answer is outside my "I need to read the reasons the C++ construct is as good as or even better the original C construct" requirement.Circumlunar
printf (besides being faster) also is much easier to type in. All the closing and reopening quotes (for cout) really slows you down.Wingback
Yep. There's also the question of verbosity - setw, setprecision and friends versus the compact field width and precision modifiers for % conversions.Know
"less to type" seems to be a spurious claim too, given that in the above example, the iostream code would be precisely the same number of characters, except that the "return 0" from main was left out...Dissuasive
The verbosity of iostream drives me mental and I refuse to use it in anything but the most trivial cases. To wit: printf("0x%08xn", x); vs std::cout << std::hex << std::setfill('0') << std::setw(8) << x << std::dec << std::endl;Tomchay
I agree with all the posts about iostream's verbosity. C++ iostreams are safer than any C counterpart (the result will always be Ok, i.e., no buffer overrun and no truncated string), but it is more verbose and slower. I guess this should be mentionned in the answer.Circumlunar
(sn)printf is also better for i18n, at least with gettext, because the translator gets the whole string and not only some snippets of it. He can even reorder the values if needed.Hobnail
I have to admit, I've always found format strings pretty confusing, primarily because I've never really used C-style IO. Having to specify whether I'm printing a double or an int seems redundant and error-prone, especially in templates. As for the performance issues, my understanding is that most of the performance gap is caused by people using std::endl for newline characters rather than just '\n', and the rest can be fixed by using std::cout.sync_with_stdio(false). See the timings in the answer to #1925030Dubuffet
C
5

Following fizzer's post at C++ constructs replacing C constructs, I'll write here my answer:

Warning: The C++ solution proposed below is not standard C++, but is an extension to g++ and Visual C++, and is proposed as a standard for C++0x (Thanks to Fizzer's comments about this)

Note that Johannes Schaub - litb's answer offers another, C++03 compliant way to do it anyway.

Question

How to extract the size of a C array?

Proposed C solution

Source: When are C++ macros beneficial?


#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

Unlike the 'preferred' template solution discussed in a current thread, you can use it as a constant expression:

char src[23];
int dest[ARRAY_SIZE(src)];

I disagree with Fizzer as there is a templated solution able to generate a constant expression (In fact, a very interesting part of templates is their capacity to generate constant expressions at compilation)

Anyway, ARRAY_SIZE is a macro able to extract the size of a C array. I won't elaborate about the macros in C++: The aim is to find an equal or better C++ solution.

A better C++ solution?

The following C++ version has none of the macro problems, and can do anything the same way:

template <typename T, size_t size>
inline size_t array_size(T (&p)[size])
{
   // return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

demonstration

As demonstrated by the following code:

#include <iostream>

// C-like macro
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

// C++ replacement
template <typename T, size_t size>
inline size_t array_size(T (&p)[size])
{
   // return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

int main(int argc, char **argv)
{
   char src[23];
   char * src2 = new char[23] ;
   int dest[ARRAY_SIZE(src)];
   int dest2[array_size(src)];

   std::cout << "ARRAY_SIZE(src)  : " << ARRAY_SIZE(src) << std::endl ;
   std::cout << "array_size(src)  : " << array_size(src) << std::endl ;
   std::cout << "ARRAY_SIZE(src2) : " << ARRAY_SIZE(src2) << std::endl ;
   // The next line won't compile
   //std::cout << "array_size(src2) : " << array_size(src2) << std::endl ;

   return 0;
}

This will output:

ARRAY_SIZE(src)  : 23
array_size(src)  : 23
ARRAY_SIZE(src2) : 4

In the code above, the macro mistook a pointer for an array, and thus, returned a wrong value (4, instead of 23). The template, instead, refused to compile:

/main.cpp|539|error: no matching function for call to ‘array_size(char*&)’|

Thus demonstrating that the template solution is: * able to generate constant expression at compile time * able to stop the compilation if used in the wrong way

Conclusion

Thus, all in all, the arguments for the template is:

  • no macro-like pollution of code
  • can be hidden inside a namespace
  • can protect from wrong type evaluation (a pointer to memory is not an array)

Note: Thanks for Microsoft implementation of strcpy_s for C++... I knew this would serve me one day... ^_^

http://msdn.microsoft.com/en-us/library/td1esda9.aspx

Edit: The solution is an extension standardized for C++0x

Fizzer did rightly comment this was not valid in the current C++ standard, and was quite true (as I could verify on g++ with -pedantic option checked).

Still, not only this is usable today on two major compilers (i.e. Visual C++ and g++), but this was considered for C++0x, as proposed in the following drafts:

The only change for C++0x being probably something like:

inline template <typename T, size_t size>
constexpr size_t array_size(T (&p)[size])
{
   //return sizeof(p)/sizeof(p[0]) ;
   return size ; // corrected after Konrad Rudolph's comment.
}

(note the constexpr keyword)

Edit 2

Johannes Schaub - litb's answer offers another, C++03 compliant way to do it. I'll copy paste the source here for reference, but do visit his answer for a complete example (and upmod it!):

template<typename T, size_t N> char (& array_size(T(&)[N]) )[N];

Which is used as:

int p[] = { 1, 2, 3, 4, 5, 6 };
int u[sizeof array_size(p)]; // we get the size (6) at compile time.

Many neurons in my brain fried to make me understand the nature of array_size (hint: it's a function returning a reference to an array of N chars).

:-)

Circumlunar answered 22/10, 2008 at 20:25 Comment(9)
csci.csusb.edu/dick/c++std/cd2/expr.html . See 5.19 Constant Expressions: 'functions ... shall not be used, and ... function-call ... operators shall not be used'Know
Your link is about a "November 1996 Working Paper" draft: csci.csusb.edu/dick/c++std/cd2 ... My own links are 2008 and are about C++0xCircumlunar
I'm aware of that. The wording of the referenced paragraph is identical to the current standard.Know
You're right. So I guess we all agree on the fact that today, the code above is an extention to C++ offered by g++ and Visual C++, and that the C++0x will most probably include something like that (i.e. using the constexpr keyword) for the next standard.Circumlunar
No problem. Thanks for making the correction so prominently.Know
@Fizzer: Thanks for your contribution. I learned I lot today, thanks to the discussion we shared.Circumlunar
Er … why don't you simply return the size template argument in your function? The calculation of the size is completely redundant.Waterage
Good point. While this is not important (the calculation is done at compile time), I guess that Konrad is right and that the "size" parameter could be used directly. I'll verify this and correct the code.Circumlunar
you can have a fully standard compliant constant expression by using this one. no need to wait for c++0x :) : template<typename T, size_t N> char (& array_size(T(&)[N]) )[N]; int dest2[sizeof array_size(src)];Allan
B
4

casting the C way (type) versus static_cast<type>(). see there and there on stackoverflow for the topic

Basically answered 22/10, 2008 at 20:25 Comment(2)
You should make a summary, detailing with one sentence the exclusive use of each C++ cast, and finishing with something like "In C++, we can limit the cast's scope (const, static, etc.) and it's easier to find/grep in a large project".Circumlunar
Right, but since you were the one who ask and that now I see that you are aware of those points on one hand. And, in the other hand, C++ cast are well covered by the ther two posts of which I provide links in my response, I think it's better to let the it concise. Anyhow thanks for the advice.Basically
S
4

Local (automatic) variables declaration

(Not true since C99, as correctly pointed by Jonathan Leffler)

In C, you must declare all local variables at the start of the block in which they are defined.

In C++ it is possible (and preferable) to postpone variable definition before it must be used. Later is preferable for two main reasons:

  1. It increases program clarity (as you see the type of variable where it is used for the first time).
  2. It makes refactoring easier (as you have small cohesive chunks of code).
  3. It improves program efficiency (as variables are constructed just when they actually needed).
Scevour answered 23/10, 2008 at 13:7 Comment(4)
It also produces small, cohesive nuggets of code that make editing/refactoring easier.Kittykitwe
It also frequently delays variable declaration until the variable can be intelligently initialized. This is a reduction in exposure, and if the variable is not of a basic type can lead to performance improvements.Herbst
I find this feature provides much cleaner code. This feature makes functional decomposition much easier. So much so that in C I will wrap my variables in { } to trap them to the minimum of lines possible. It's even worth using tiny {} block in C++.Enswathe
This is bogus - C99 (nearly a decade old now) provides C++-style declaration of variables at any point in a function. The number of people who use it, though, is limited.Bobinette
J
2

In response to Alex Che, and in fairness to C:

In C99, the current ISO standard spec for C, variables may be declared anywhere in a block, the same as in C++. The following code is valid C99:

int main(void)
{
   for(int i = 0; i < 10; i++)
      ...

   int r = 0;
   return r;
}
Johanson answered 29/10, 2008 at 0:33 Comment(0)
P
2

I'll offer something that is perhaps abundantly obvious, Namespaces.

c's crowded global scope:

void PrintToScreen(const char *pBuffer);
void PrintToFile(const char *pBuffer);
void PrintToSocket(const char *pBuffer);
void PrintPrettyToScreen(const char *pBuffer);

vs.

c++'s definable subdivisions of global scope, namespaces:

namespace Screen
{
   void Print(const char *pBuffer);
}

namespace File
{
   void Print(const char *pBuffer);
}

namespace Socket
{
   void Print(const char *pBuffer);
}

namespace PrettyScreen
{
   void Print(const char *pBuffer);
}

This is a bit of a contrived example, but the ability to classify tokens you define into scopes that make sense prevents confusing purpose of the function with the context in which it is called.

Pantin answered 31/12, 2008 at 2:50 Comment(0)
A
2

Following paercebal's construct using variable length arrays to get around the limitation that functions can't return constant expressions yet, here is a way to do just that, in a certain other way:

template<typename T, size_t N> char (& array_size(T(&)[N]) )[N];

I've written it in some of my other answers, but it doesn't fit anywhere better than into this thread. Now, well, here is how one could use it:

void pass(int *q) {
    int n1 = sizeof(q); // oops, size of the pointer!
    int n2 = sizeof array_size(q); // error! q is not an array!
}

int main() {
    int p[] = { 1, 2, 3, 4, 5, 6 };
    int u[sizeof array_size(p)]; // we get the size at compile time.

    pass(p);
}

Advantage over sizeof

  1. Fails for non-arrays. Will not silently work for pointers
  2. Will tell in the code that the array-size is taken.
Allan answered 4/1, 2009 at 20:12 Comment(2)
I've been reading and testing your code for 10 minutes, and I'm still wondering what in the hell is array_size (I'm not that good at parsing C++ types, I guess). Anyway, +1 for the gizmo (and for the time I'll spend trying to learn/understand it)Circumlunar
Ok, I got it. It's the prototype of a function returning the reference to an array of N chars. The sizeof operator is used to get the size of the returned array of chars. I did not know the sizeof operator could be used on function prototypes to return the size of its returned type. Thanks!Circumlunar
D
1

std::copy vs. memcpy

First there are usability concerns:

  • memcpy takes void pointers. This throws out type safety.
  • std::copy allows overlapping ranges in certain cases (with std::copy_backward existing for other overlapping cases), while memcpy does not ever allow it.
  • memcpy only works on pointers, while std::copy works on iterators (of which pointers are a special case, so std::copy works on pointers, too). This means that you can, for example, std::copy elements in a std::list.

Surely all of this extra safety and generality comes at a price, right?

When I measured, I found that std::copy had a slight performance advantage over memcpy.

In other words, it seems as if there is no reason to use memcpy in real C++ code.

Dubuffet answered 22/10, 2008 at 17:48 Comment(0)
C
0

Overloaded functions:

C:

AddUserName(int userid, NameInfo nameinfo);
AddUserAge(int userid, int iAge);
AddUserAddress(int userid, AddressInfo addressinfo);

C++ equivalent/replacement:

User::AddInfo(NameInfo nameinfo);
User::AddInfo(int iAge);
User::AddInfo(AddressInfo addressInfo);

Why it's an improvement:

Allows the programmer to express the interface such that the concept of the function is expressed in the name, and the parameter type is only expressed in the parameter itself. Allows the caller to interact with the class in a manner closer to an expression of the concepts. Also generally results in more concise, compact and readable source code.

Convexoconvex answered 24/10, 2008 at 16:12 Comment(3)
While the pass-by-copy would probably be better as pass-by-const-reference, I've seen enough of this pattern to believe the non-use of overloaded function in stead really was a "I'm not comfortable with C++ overloaded functions" statement. +1. (Who the hell downmodded you anyway?)Circumlunar
@parcebal: I did, for one. These functions should be named differently. What's the "C++ equivalent/replacement" for AddUserGrade(int userid, int grade)?Tweeter
I've found that when dealing with anything but the simplest and smallest cases the idea to have different functions with the same name is stupid and makes the code harder to follow for no real gain.Paine
D
0

iostreams

Formatted I/O may be faster using the C runtime. But I don't believe that low-level I/O (read,write,etc.) is any slower with streams. The ability to read or write to a stream without caring if the other end is a file, string, socket or some user-defined object is incredibly useful.

Denham answered 24/10, 2008 at 21:16 Comment(1)
Do you have an example of code enabling a junior developer to compare the two versions?Circumlunar
P
0

In c, much of your dynamic functionality is achieved by passing about function pointers. C++ allows you to have Function Objects, providing greater flexibility and safety. I'll present an example adapted from Stephen Dewhurst's excellent C++ Common Knowledge

C Function Pointers:

int fibonacci() {
  static int a0 = 0, a1 =1; // problematic....
  int temp = a0;
  a0 = a1;
  a1 = temp + a0;
  return temp;
}

void Graph( (int)(*func)(void) );
void Graph2( (int)(*func1)(void), (int)(*func2)(void) ); 

Graph(fibonacci);
Graph2(fibonacci,fibonacci);

You can see that, given the static variables in the function fibonacci(), the order of execution of Graph and Graph2() will change the behavior, not withstanding the fact that call to Graph2() may have unexpected results as each call to func1 and func2 will yield the next value in the series, not the next value in an individual instance of the series with respect to the function being called. (Obviously you could externalize the state of the function, but that would be missing the point, not to mention confusing to the user and complicating to the client functions)

C++ Function Objects:

class Fib {
  public:
    Fib() : a0_(1), a1_(1) {}
    int operator();
  private:
    int a0_, a1_;
};
int Fib::operator() {
    int temp = a0_;
    a0_ = a1_;
    a1_ = temp + a0_;
    return temp;
}


template <class FuncT>
void Graph( FuncT &func );

template <class FuncT>
void Graph2( FuncT &func1, FuncT &func2); 

Fib a,b,c;
Graph(a);
Graph2(b,c);

Here, the order of execution of the Graph() and Graph2() functions does not change the result of the call. Also, in the call to Graph2() b and c maintain separate state as they are used; each will generate the complete Fibonacci sequence individually.

Pantin answered 31/12, 2008 at 3:23 Comment(0)
A
0

new in C++ vs malloc in C. (for memory management)

new operator allows class constructors to be called whereas malloc does not.

Ammoniacal answered 4/1, 2009 at 20:39 Comment(0)
W
-4

Nearly any use of void*.

Waters answered 22/10, 2008 at 18:0 Comment(1)
You should offer an example of C code at its maximum safety using void *, and the example of C++ code using whatever solution to offer the same solution. This would upmod your post.Circumlunar

© 2022 - 2024 — McMap. All rights reserved.