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...
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:
cout << "This is inside f\n";
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?
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.
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
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
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.
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
int* p = newint(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.
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