What do you like and dislike about C++

Pages: 12
1. Templates syntax: They're murder on the compiler and they're constantly tip-toeing around older C syntax. They're also not very easy on the eyes.

2. Traits: C++ lacks built-in support for type traits. Can be pretty painful. I also wish C had these to some extent too.

2. C compatibility: C++ would look closer to D with less silliness and a standard if it didn't try and contain compatibility with C (which it doesn't entirely but enough to where code is mildly simple to port). Codebases that are based on C and change to C++ should have been re-written instead of "ported"... since you end up with tid bits of weird code all over the place that are remnants of the C base.

3. Lambdas: WTF is that syntax? It's so hard on the eyes, it usually takes me a few reads just to realize it's a damn lambda declaration.

4. Grammar: C++ is a bitch to parse for a few reasons. It's almost (*almost*) impossible to parse in an LL(1) grammar and you have to use some hackery to get it done (which is why, I'm guessing, a lot of modern compilers use hand-written recursive descent parsers). For instance, you can't determine, from the top-level of a translation unit, what you're about to parse in one symbol. You have to either look ahead more than one symbol, or you have to use a rule that can be used for multiple matches (which you would do for LL(1) until you can determine what you're parsing finally). This is slow, it's error prone, it's confusing, and it's hard to implement. This is mostly stemming from the C grammar by the way. Templates, lambdas, and various other C++isms are a completely different nightmare to parse...

Probably more, I just can't think of any right now...
@NoXzema

2. Traits: C++ lacks built-in support for type traits.


Wasn't sure what you meant by that:

http://en.cppreference.com/w/cpp/types


They're implemented using templates... It's a minor complaint but it's slow and can cause obfuscated errors.

EDIT: By slow, I mean it slows down compilation speed due to heavy templating. I'm aware it's not a runtime mechanism...
Last edited on
It depends on the specific trait. Some traits, such as is_enum are implemented with compiler intrinsics.
I really like the fact that you can write code that is very small if you want, or interacts with the hardware if you want, or is very abstract and portable if you want. It's a good language for almost any problem.

One thing I've noticed is that beginners often write code that compiles but has a completely different effect from what they expect. Here's a program that demonstrates a few of them:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>
#include<string.h>
#include <iostream>

using std::cout;

int i,j,k;

void f()
{
    cout << "This is inside f\n";
}

int
main(void)
{
    i,j,k = 1,2,3;

    // Why doesn't this print "1 2 3"?
    cout << i << ' ' << j << ' ' << k << '\n';

    // Why is the first line below right and the second one is wrong?
    cout << "50% of 18 is " << 18 * 50 / 100 << '\n';
    cout << "50% of 18 is " << 50/100*18 << '\n';

    // why won't my function call work?
    void f();

}

0 0 1
50% of 18 is 9
50% of 18 is 0

Compiler warnings help but I can tell that it's really frustrating for beginners when this stuff happens.

To me, a really good language should make it hard to say the wrong thing accidentally.
I really do hate the C compatibility requirement, but at the same time it really is a godsend.
Converting a large ( > 1 million) legacy C codebase to idiomatic C++ really isn't feasible.

By being able to more or less mindlessly "switch" languages, it really helps me out and allows me to convert each individual module one at a time which is much, much more manageable.

I don't see how something like this would leave any artifacts in a C++ codebase. You should really be exposing a C interface between modules anyway.
like c++ for


bool type with true and false values (C has no single 'true' value)
passing parameters by reference (instead of by pointer)
using new/delete instead of malloc/free
for( int i = 0; i < n; i++ )
declare a variable immediately before the (first) statement that uses it
local arrays that have a calculated size
bool type with true and false values (C has no single 'true' value)

C does have a boolean type (_Bool).

using new/delete instead of malloc/free

Why is this better? There's no reason for new/delete in C.

for( int i = 0; i < n; i++ )

You can do this in C.

declare a variable immediately before the (first) statement that uses it

You can do this in C also.

local arrays that have a calculated size

Actually, C++ doesn't allow variable length arrays. C explicitly allows it, one of the various areas of minor incompatibilities.
cowbell wrote:
using new/delete instead of malloc/free


Probably shouldn't be using new / delete in C++ either, use smart pointers.

http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique

The trouble with new is that if an exception occurs the destructor is never reached, or any other situation where delete is never called - so we have a memory leak.
The trouble with new is that if an exception occurs the destructor is never reached
Do you mean an exception inside the constructor? The destructor is never called in that case, even if new is not used.
@helios

Perhaps I should have said that use of new is not exception safe. I see what you mean about the destructors in terms of stack unwinding, but in any case the memory from the new is still leaked.

If initialization terminates by throwing an exception (e.g. from the constructor), if new-expression allocated any storage, it calls the appropriate deallocation function: operator delete for non-array type, operator delete[] for array type. ... http://en.cppreference.com/w/cpp/language/new


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <new>

template < int TAG > struct A
{
    explicit A( int v ) { if( v == TAG ) throw TAG ; std::cout << "A<" << TAG << ">::constructor\n" ; }
    ~A() { std::cout << "A<" << TAG << ">::destructor\n" ; }
};

struct B
{
    A<0> a0 ;
    A<1> a1 ;

    explicit B( int v ) : a0(v), a1(v) { if( v == 100 ) throw 100 ; std::cout << "B::constructor\n" ; }
    ~B() { std::cout << "B::destructor\n" ; }

    static void* operator new( std::size_t sz ) { std::cout << "allocate raw memory\n" ; return ::operator new(sz) ; }
    static void operator delete( void* p ) { std::cout << "deallocate memory\n" ; ::operator delete(p) ; }
    static void* operator new[]( std::size_t sz ) { return operator new(sz) ; }
    static void operator delete[]( void* p ) { operator delete(p) ; }
};

int main()
{
    for( int v : { -1, 0, 1, 100 } )
    {
        std::cout << "\n------   v == " << v << "   -----\n" ;
        try { auto p = new B(v) ; delete p ; }
        catch( int e ) { std::cout << "*** caught exception " << e << " ***\n" ; }
    }
}

------   v == -1   -----
allocate raw memory
A<0>::constructor
A<1>::constructor
B::constructor
B::destructor
A<1>::destructor
A<0>::destructor
deallocate memory

------   v == 0   -----
allocate raw memory
deallocate memory
*** caught exception 0 ***

------   v == 1   -----
allocate raw memory
A<0>::constructor
A<0>::destructor
deallocate memory
*** caught exception 1 ***

------   v == 100   -----
allocate raw memory
A<0>::constructor
A<1>::constructor
A<1>::destructor
A<0>::destructor
deallocate memory
*** caught exception 100 ***

http://coliru.stacked-crooked.com/a/236ca36620cf579d
If new didn't deallocate the allocated memory when an exception is throw before it returns the new pointer, it could simply not be used at all in code that required exception-safety. Even std::make_unique() and std::make_shared() would not be exception-safe.
Even std::make_unique() and std::make_shared() would not be exception-safe.


Aren't those functions very carefully crafted so that it does always delete it's new pointer ?

Anyway the example I originally had in mind was this one from cppreference new:
http://en.cppreference.com/w/cpp/language/new

If the original value of pointer is lost, the object becomes unreachable and cannot be deallocated: a memory leak occurs.

This may happen if the pointer is assigned to:

2 other examlpes elided
.
.
.
or due to exception

1
2
3
4
5
6
void f()
{
   int* p = new int(7);
   g();      // may throw
   delete p; // okay if no exception
} // memory leak if g() throws 


To simplify management of dynamically-allocated objects, the result of a new-expression is often stored in a smart pointer: std::auto_ptr (until C++17)std::unique_ptr, or std::shared_ptr (since C++11). These pointers guarantee that the delete expression is executed in the situations shown above.
Aren't those functions very carefully crafted so that it does always delete it's new pointer ?
Since new does not leak the pointer if the constructor throws, no, there's no real need to do that. They can simply be written as wrappers for std::/*...*/_ptr<T>(new T(/*...*/)).
std::make_unique is a simple wrapper over
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) ) ;

std::make_shared and std::allocate_shared are required to be more elaborate:
1
2
3
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
template<class T, class A, class... Args>
shared_ptr<T> allocate_shared(const A& a, Args&&... args);

...
Effects: Allocates memory suitable for an object of type T and constructs an object in that memory via the placement new-expression ::new (pv) T(std::forward<Args>(args)...). The template allocate_shared uses a copy of a to allocate memory. If an exception is thrown, the functions have no effect.
...
Remarks: Implementations should perform no more than one memory allocation.
[Note: This provides efficiency equivalent to an intrusive smart pointer. —end note ]

[Note: These functions will typically allocate more memory than sizeof(T) to allow for internal bookkeeping structures such as the reference counts. —end note ] - IS
Topic archived. No new replies allowed.
Pages: 12