In 1986 or so, Bjarne Stroustrup famously said: "C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off."
What is, in your opinion, the most spectacular way to blow your leg off in C++? Points for originality, and for helpfulness.
Originality, eh? Well how about Multi-Dimensional Analog Literals
Tighten your seatbelt and click here [1]
There is real code behind these (see bottom of post). I only wish I had an ounce of that much creativity.
Example:
unsigned int c = ( o-----o
| !
! !
! !
o-----o ).area;
assert( c == (I-----I) * (I-------I) );
[1] http://www.xs4all.nl/~weegen/eelis/analogliterals.xhtmlAttempting to call a pure virtual function from a constructor or destructor. Since the derived class object has not yet been constructed (or has already been destroyed), this will result in badness. The compiler will convert virtual function calls into static calls of the base class version's of the function when it can, but there is no base class version of a pure virtual function, and undefined behavior will result. This could happen if the call is indirect, say, by calling a non-virtual function which calls the virtual function.
10.4/6
in the C++ Standard. - Johannes Schaub - litb
Needing to change the signature of a base class virtual function and disconnecting all the derived class overrides without any complaints from the compiler.
And I used to think that C#'s insistence on 'virtual', 'overrides', 'new' modifiers was being pedantic ...
static
declared in a class cannot have a matching static
in the definition. - Michael Burr
I've initialized an array like this:
int *ip = new int(10);
Hijinks ensue when you try to use this "array" beyond index 0 (which is usually almost immediately). The problem is that the above code is the syntax to create a pointer to a new int initialized to the value 10. It only looks like the syntax for pointer to a new array of size 10.
Overloading of operators in general, but particularly new
, new[]
, delete
, delete[]
, and []
.
I've seen this a few times where someone used memset() inside the ctor to easily initialize all the class variables.
myClass::myClass()
{
memset(this,0,sizeof (myClass));
}
std::map
. The memset
of course completely ruined it. Stupid thing to do. - Lightness Races in Orbit
I love C++ for the flexibility that it offers, and with the arrival of libraries such as BOOST it is becoming easier to piece together a powerful application with most of the time spent concentrating on the business logic.
However, here are some common examples where code will compile but not do what was intended.
1) Overriding Functions
class Base {
virtual void foo () const
{
// Default beahviour
}
};
class Derived : public Base {
virtual void foo ()
{
// Do something useful for Derived
}
};
It was almost certainly not the intention of the author that 'foo' in the derived class does not override the 'foo' in the base. You have to make sure your testing is really up to scratch to make sure that you can detect that Derived::foo
is not being called!
2) Operators that short-cut except when they don't:
class A {};
A foo (int * i)
{
*i = 0;
}
bool operator && (int *, A const &);
// ... lots of space between declaration and usage
void bar (int * i)
{
if (i && foo(i))
{
}
}
Others have mentioned operators, but there are 4 which are especially problematic. In the above example rather than having the usual "evaluate the first operand and if true then the second" what we actually have is a function call operator&&(i, foo(i))
. The first argument i
and the second foo(i)
. Both will be evaluated (in some unspecified order) and this will result in a null pointer dereference in foo
. The other operators to watch out for are, logical or (||), comma (,) and unary &.
3) Class member initialization that doesn't initialize:
class B
{
public:
B ();
};
class A
{
public:
~A();
private:
B b;
int i;
};
The above class the non-POD member i
is not initialized by the default constructor for the class. The same applies if the constructor was written as:
A ()
{
}
or
A ()
: b ()
{
}
Of course - what you need is some form of static analysis tool which will catch all of these niggling problems for you! ;)
In C/C++, if you start a numeric constant with a zero, it's interpreted as octal:
int a = 123; // Decimal 123
int b = 0123; // Octal 123, decimal 83
I've never seen this result in a bug, but once when I was hand-editing a large block of data for an array, I typed leading zeros for some of the constants just to pad them to length. If none of them had an 8 or 9 (which is invalid for octal), it would have compiled cleanly and definitely would have not worked right.
It doesn't happen often, but I always find this hard to track down.
if (number = 5) {
// code
}
if (0 == callThatCanFail()){...}
despite the rough way it reads - dmckee
Here on SO [1], Joel Coehoorn wrote about this example:
if ( blah(), 5) {
//do something
}
[1] http://stackoverflow.com/questions/149500/what-does-the-code-if-blah-5-do#149514"Note that the , operator could be overloaded for the return type of the blah() function (which wasn't specified), making the result non-obvious."
Accidentally instantiating an RAII object as a temporary, for example:
boost::mutex::scoped_lock(m_mutex);
instead of
boost::mutex::scoped_lock guard(m_mutex);
No errors, no warnings, just mysterious intermittent runtime errors.
Not initializing a pointer and then using it later.
Programming serious C++ before you understand it. Sadly it's reputation for being an experts only language is well deserved.
Please, please, please read Effective C++ [1] before you touch production code.
[1] http://rads.stackoverflow.com/amzn/click/0321334876Returning references to temporaries. Usually this happens when you accidentally copy-construct along the way. Something like:
string MyVal()
{
return _val;
}
string& GetVal()
{
return MyVal();
}
While this is not foot shooting, I flipped out when I saw a really experienced C++ programmer do this:
((SomeClass*)NULL)->SomeMethod();
Which is actually perfectly fine, as long as SomeMethod
doesn't access the this
pointer or call any member functions (which in this case it didn't).
Still, that kind of thing is pretty much GUARANTEED to blow the leg off the next programmer that modifies that class.
Not having a virtual destructor on a base class.
Overriding ::new() operator. You might have grand hopes of tracking memory leaks or logging fragmentation or whatever. It almost always turns south quickly.
Something to blow the whole leg for sure, with std::map for example:
...
for ( pos = c.begin(); pos != c.end(); ++pos) {
if ( pos->second == something) c.erase( pos);
}
...
Using auto_ptr not knowing (or forgeting) that the assignment operator transfers ownership
void Foo(auto_ptr<string> val)
{
std::cout << *val;
}
int main()
{
auto_ptr<std::string> a (new std::string("abc"));
auto_ptr<std::string> b = a; // a is erased !
Foo(b); // b is erased also !!! (always use references in method signatures with auto_ptr)
...
}
Violating the Principle of least astonishment [1]. For example operator + should add things together. If you have it do subtraction it will really confuse people and cause them to make stupid mistakes. Or as Scott Meyers put is, do as the ints do.
Edit: the quote from Meyers is from Effective C++, in my copy of the 3rd edition it's in item 18 page 80. "Clients already know how types like int behave, so you should strive to have your types behave in the same way whenever reasonable...When in doubt, do as the ints do." Go read/re-read item 18 today, and be a better programmer!
[1] http://en.wikipedia.org/wiki/Principle_of_least_astonishmentCasting a pointer to a type that something isn't. Then calling a virtual function on that type. Depending on how "lucky" you are there may be a function at that location in the vtable that has the same parameters. I've done this where the debugger was stepping through the wrong code. Totally perplexing. If you aren't so "lucky" then it will just crash with a corrupted stack depending on the calling conventions.
Using multiple inheritance [1] is a good way ensure that you'll have problems.
[1] http://en.wikipedia.org/wiki/Multiple_inheritanceModify a std::map while you have an active iterator on it. I spent 3 days debugging that once.
In a project that does not use namespaces, create a class named Thread with a method called run(). Then make the project use a shared library that, unbeknown to you, also has no namespaces and a class named Thread with a method named run().
No try to figure out that why when you create a thread in your code and pass a pointer to the Thread::run() method as the main body of the thread, your threads never get created.
Good times!
Calling a function like this:
int result1 = f( i, i += 2 );
leads to unpredictable results. The function parameter evaluation order isn't specified in the C++ spec, so you don't know if the function will get the current value of i for the first parameter, or if it will be the value of i after i += 2 has been evaluated.
Circular header dependencies.
If you're not familiar with the pattern of errors that arise from this situation you can go on for hours trying to figure out what the hell is going on.
uses
in the interface and uses
in the implementation. In C++, headers (should) contain the interfaces and .cpp files (should) contain the implementation. (continued) - Eduardo León
#define true false
Different versions of struct in different modules, either because struct is defined in two places, or because the definition has changed and one of the modules was not recompiled (dependencies error). This can cause some fun behavior, such as the value of a field being different inside and outside a function call (we saw it in this [1] question).
[1] http://stackoverflow.com/questions/161490/referenced-structure-not-stickingthe dreaded trailing semicolon after a for loop:
for(i=0;i<1000;++i);
print("i=%d",i);
output is 'i=1000'
This:
MyObject* DoSomethingNotSoClever()
{
MyObject object;
return &object;
}
Doh, object gets destructed and becomes invalid the moment this function returns. If the caller tries to access the returned pointer, they're accessing garbage. It may work for a while until at some time later everything will go bang.
Operator overloading can be pretty evil. Say you overloaded the operator '*' and you need to modify the implementation/contract. Now, being a good coder, you'll go and check all the uses of the overloaded operator.
Ever tried grep'ing for '*' over a large codebase?
Relying on default parameters in virtual functions in derived classes.
Forgetting that == has precedence over bitwise operators like & and |. As in:
if (x == y&1)
{
MyObject* A = NULL;
MyObject* B = NULL;
if (A == B == NULL)
{
//do something
}
else
{
//do something else
}
You'd wish this if-statement would be the equivalent of saying
if ((A==NULL) && (B == NULL))
but you'd be wrong.
delete this;
I can't begin to describe the pain and suffering caused by that one line of code.
Obfuscation. Things like automatic constructors that do too much, overriding operators, throwing exceptions, etc. Even macros to some extent.
C's beauty is that you can look at a snippet of code and have a very good idea of what the assembly does. Java's (or C#'s) beauty is that you don't have to. C++ can be abused to become the worst of these two worlds -- code that needs to be understood at the level of assembly, but is opaque.
Using default values for fuction void Func(int i = 0, bool x = false){...}
which is only declared in the header file.
Then trying to track the place where you get false from the 2nd parameter.
Returning pointers to local variables:
const char* getStr(...)
{
char buf[128];
return buf;
}
I hate that c++ allows you to do stuff like that without any kind of warning. It is the only thing that needs fixing in c++ - error reporting and warnings.. C++ was apparently programmed by people with mentality "He who make mistakes must suffer"...
Not understanding pointers is the quickest and easiest way to shoot yourself in the foot using C++.
From my C++ days back in the 90s, I was always a fan of
MyType<MyOtherType<T>>
Hello right-shift !
If you have a a multithreaded design of any sophistication, and you feel like a few hours tracking down object lifecycle race conditions, try using raw pointers for object ownership.
Smart pointers don't make threads easy, but they certainly help.
int x = 7;
int y = 2;
float result = x / y;
You might think result would be 3.5, but it's 3!
The most spectacular way would be to use template driven meta programming. A simpler way would be to override operators and use them without specifying '(' ')'.
On a different note, modifying the header files without changing the .cpp files should also do the trick. Some call this incomplete coding.
I cut my teeth on assembly, then C, then C++, then C#. I think of C as a clean set of macros for an assembly language. Which I actually like, because I understand every nook and cranny of it. C++ opens up a ton more tricks, but everything in C is still also available. C# finally got wise to this, and even though it still looks very C-like, it blocks some low-level C-type constructs.
One of the thorniest problems I ran into on a huge C++ app turned out to be malloc/free versus new/delete. Anything that's malloced has to be freed, and anything that's newed has to be deleted. But the compiler can't launch flares if something was inadvertently mix-and-matched, so bizarre memory corruption problems can lurk for years. Good luck finding that in your debugging sessions or code reviews.
Using raw pointers without documenting ownership. Two typical cases are having a function that returns a pointer without documenting whether the caller takes ownership of it, or having a function that takes a pointer as a parameter without documenting whether it takes ownership of the pointer. (The entity that has "ownership" of a pointer is responsible for deleting it.)
Using smart pointers is a good way to avoid these problems.
Creating a buffer overrun vulnerability.
There are few things worse than opening up a code execution exploit to any user who can input data into your program.
static void MyInterruptServiceHandler(/* ... */)
{
SomeObject p; // allocates memory in the constructor
// whoops! There goes my heap!
}
while (1) {
// lots of condition tests that don't cover every condition.
}
Non-const references as arguments are evil as it's non-obvious (from just reading the code at the callsite) that an argument is potentially being modified.
E.g., given:
void do_something(int& foo);
and from reading code at the callsite:
int x = 5;
do_something(x);
it's non-obvious that x could be modified by do_something().
Unnecessary use of threads.
Some of the things described in this quiz [1]
[1] http://www.scapecode.com/2008/08/a-simple-c-quiz.htmlOverloading * or ->
If you go above or below the size of your arrays, then C/C++ will access different things in memory. For example the following code will replace the value of a variable in outside the function.
#include <stdio.h>
#include <stdlib.h>
void f(int x) {
int a[10];
printf("a[20] is at address 0x%x\n",(int)&a[20]);
a[20] = -1; /* change variable answer in main (gcc4.3.2/linux/i86) */
}
int main(void) {
int answer = 42;
printf("answer is at address 0x%x\n",(int)&answer);
f(5);
printf("answer=%d\n", answer);
return 0;
}
Even worse is the following, which will change the place the function returns to, to skip a password checking if statement:
#include <stdio.h>
#include <stdlib.h>
int check_password() {
char buffer[8];
printf("Enter password: ");
gets(buffer);
int i;
int * p = (int *) & buffer[20];
printf("*p is %x\n",*p);
*p += 4;
/* change function's return address on stack (gcc 4.3.2/Linux/i86) */
return strcmp(buffer, "secret") == 0;
}
int main(int argc, char *argv[]) {
int k = 7;
printf("size of address %d\n",sizeof(int *));
printf("function main is at address 0x%x\n", &main);
if (check_password()) {
printf("Authenticated\n");
} else {
printf("Password Incorrect\n");
}
printf ("bye\n");
}
Let's see, some interesting things you can do.
Non-explicit type conversions, including constructors. If your class Foo can be converted into a Bar, like Bar::Bar(Foo f)
, then any function that works on a Bar will work on a Foo, not necessarily correctly. Something like explicit Bar::Bar(Foo f)
will work much better.
Overloaded functions that do different conceptual things, depending on the type of the operands. A function Draw(...)
that put images on the screen for most things but deployed a gun when used on another thing would be an example.
Similarly, operators that do non-obvious things. operator+()
works fine for adding ints and floats and other sorts of numbers or vectors or whatever, or for concatenating strings, but if you use it for anything else, you're setting yourself up for trouble. A related case is turning short-circuit operators into functions, such as operator&&()
, which will now evaluate both its operands rather than the first and then possibly the second.
You could write a firework launch system in C++ that launches fireworks automatically in time to a piece of loud classical music. Then accidentally stand in front of it with your foot in the way during the finale.
It would be spectacular.
Note (more detail as requested by TonJ): If you work on a system like this, I recommend you avoid try/catch semantics. Using a return for a firework that failed ignition is also a no-no. And, make sure any pointers are initialized to be away from any observers.
Not calling delete or delete[]
An infinite loop :P
Also, leaving orphan nodes is a pretty rocking way to hurt yourself.
By using C++.
Sorry, used it a lot, but this is the worst language ever. If you really really have to be low level, use C. If not, use a real OO language (one with Garbage Collection, C#, Java) or a dynamic language if you are higher level than that.
The only language I can name right now that I would not choose for any purpose is C++
Programming in C++ it's virtually impossible to think in OO because you have to track GC. I've virtually never seen good OO c++ code. (It's also somewhat annoying to create new classes because of header files, so most C++ classes seem to be longer and manage more than one concern, another bad OO concept).
I've used C++ and still use it occasionally, but not for OO code, and would never choose it for a new project where the language wasn't set already. I've worked two embedded systems where Java did most of the work including a spectrum analyzer (pretty much an o-scope) where even the trace was drawn in Java.
My point was, C++ doesn't really offer any advantages (It's about twice as fast for the CPU usage, I can't recall the last time I was near 50% cpu usage on one app for more than a second), and has so many ways to shoot yourself in the foot that it's not even funny.
Actually there is another advantage--C++ is the last really hard language and it keeps the barrier to entrance higher. I don't know a lot of C++ programmers who don't understand the language, and many are excellent programmers--so (as opposed to Java, C++, VB and Ruby) C++ apps tend to be a bit more consistently good.