C++ pitfalls
horstmann.com
horstmann.com
map<int,int> a;
cout << a[11] << endl;
cout << a.size() << endl;
This works fine and has well defined behavior: it will print '0' followed by '1' since the middle line actually inserts a {key=11, value=0}.This is a landmine I think every journeyman c++ programmer steps on a few times.
I really wish there was an API "Hall of Shame" with attached discussion minutes showing the exact points where a group of otherwise sane people decided to kludge these things in.
It's better than the alternatives though, I would be interested to hear how you handle this in your version. Leaving the behaviour undefined for non-existent keys is likely to cause far worse bugs, throwing an exception would be inconsistent with the rest of the stl.
The could have left it out altogether, but would mean losing some nice properties - operator[] returning a reference makes it possible to assign into the map directly ( a[3] = 5; ). Also since the value is default initialized, you can write something like a counter easily, much like a python defaultdict:
for (auto id in ids) {
a[id] += 10;
}
You can always stick to .find and .insert if you prefer the more explicit behaviour.Though not consistent with how STL works, it is consistent with how our containers work and how we use maps. YMMV.
Also, since we wrote it, we're free to change the behavior if a better way manifests itself. So, if you have any suggestions, let fly.
(I don't know if that would be better, since it's even more magic and it still isn't perfect.)
Now, off to pour Courvoisier on those funny bumps in my groin...
Anyways, g++ -Weffc++ -Wall helps, eventhough not in this case I think ;)
http://cpptruths.blogspot.com/2006/08/g-compiler-option-weff...
Many of these are warned about on modern compilers, if you use '-Wextra -Wall'. Some of them are even on by default. I think compilers should warn more eagerly. I would put -Wextra -Wall on by default -- it is easier to turn off things you don't want, than to discover things you do not know exist. But I know this is not a popular opinion!
C++ exists because real high level languages does not have very efficient compilers yet.
And where high performance is really needed and programmers time or effort is not a problem C++ is preferred.
I expect that with the increasing processing power compilers will get some AI incorporated and get smarter in the future and high level languages will output binaries that are at least as fast as those produced by C++ or C compilers.
C++ mostly hangs on because of backwards compatibility and a large user base; I suspect that as time goes on, we'll see it fall more and more by the wayside (and I say this as someone whose bread and butter is C++), especially with the fact that programmer time is a big cost driver.
1. Member initialization order being broken in the constructor initialization is a warning pretty much everywhere (except Microsoft compilers, I think).
2. Having a virtual function and no virtual destructor - a warning even in Microsoft compilers.
3. Trivial infinite recursion (i.e. Manager::print() { print(); } is a warning at least in some compilers. I have seen only a handful of projects where such code existed at all so I cannot say how widespread this warning (For me it popped due to problems with scripts generating code, people don't write this stuff ).
This is just from glancing over the article, I am sure if you tried all pitfalls with a modern compiler like clang or even gcc, you'd get a few more warnings.
Though I agree with the main point the author is trying to make - C++ is not just Java with pointers so if you are coming from Java background you should be very careful not to assume the conventions you've got used in Java are the same in C++.
The C++11 revision didn't change much other than add to the already joyful cursing because now compiler support is even more of an issue than it ever was.
For example, the auto_ptr one (advice should be: don't use it).
Or the ones that use raw pointer data members and 'delete' in the destructor (for example, the one about the rule of three). This is still good advice, but needed far, far, far more rarely since we can use shared_ptr and unique_ptr to give proper copy/assignment to internal pointers. C++11 has gotten us to a point where using 'new' or 'delete' keywords, even in constructor/destructors are possible code smells.
string a{"Hello"};
string b{};
string c = string{"World"};
Many of the others also disappear if you stick to a "modern" style.class Bool { bool value; public: Bool(bool v) : value(v) {} explicit operator bool () { return value; } };
String x = "foo";
String y = "foo";
// Appears to work but compares references
System.out.println(x[0] == x[1]);
There's no substitute for knowing your tools well.I don't love Java, not by any stretch, but it's difficult to argue that Java and C++ are anywhere near equivalent in this dimension. Effective C++ is littered with examples of gotchas which are highly peculiar to C++ and the C++ compiler. It's an incredibly complex language.
Whether C++ worth these trade-offs for a particular team, project, or company is orthogonal.
System.out.println(x == y); template<typename T>
Array<T>::Array(int size)
: _size(size),
_data(new T(size)) // should have been new T[size]
{}
That's not the only thing wrong here. private:
T* _data;
int _size;
};
The order of initialization is the order of declaration, NOT the order you use in your constructor._data depends on _size being initialized first. Therefore _size must be declared above _data.
Note: I understand the next example goes over this issue. I just think this example should've been declared properly as to avoid distracting the reader from the main problem, "() vs []".
Seriously, though: I'm no expert, but many people upthread have suggested these are still applicable even in C++11. Compiler warnings help, though you still have to learn what they mean and how to fix them.