Visual Studio sucks
I quite like C++. The template and operator overloading features let me write concise, maintainable code without adding run-time overhead. Namespaces let me structure my program in an obvious way without worrying about names from different things colliding. Smart pointers and destructors mean I can decide once what my object lifetimes should be and then forget about it, without having to worry throughout the program that objects might go away too soon or too late.
That is, until I start using MS Visual Studio 2005 to write Windows applications. I spent much of this afternoon trying to work around two known bugs in its C++ compiler, which Microsoft does not intend to fix because the fixes are in MSVS 2008. (Upgrading is not an option, because I am using it to compile a binary plug-in for a program compiled with 2005.) In both cases, the workaround involved me replacing some concise and safe code with verbose code.
The first is with namespaces. In C++, you can add using namespace Foo to a
scope to specify that when resolving unqualified names to full names in
this scope, the compiler will try the namespace Foo as well as the lexically
enclosing namespace and its ancestors. So, for example:
namespace Foo {
class A {
A() { std::cout << "Foo::A" << std::endl; }
};
}
namespace Bar {
class A {
A() { std::cout << "Bar::A" << std::endl; }
};
}
namespace Baz {
using namespace Foo;
A a; // resolves to Foo::A
}
A a; // XXX
As you can see, using namespace Foo makes unqualified references to A mean
Foo::A (unless there is a class A already in scope, in which case the
reference is ambiguous). But what of the line marked XXX? According to the
standard, it should fail at compile-time, because there is no class A in
scope here. But under Microsoft's C++ compiler, you actually get Foo::A (or a
complaint about ambiguous names, if there is a class A already in scope). The
using namespace 'leaks' out of its enclosing lexical scope, polluting other
scopes. If this happens inside a header file, then when the header is later
#included, names in other files start becoming ambiguous in completely
unexpected ways. The two ways of getting around this are to using Foo::A each
class individually, or to spell out each name in full every time it is used. If
your namespaces contain lots of types, neither of these is terribly convenient.
As the file I was working on was essentially a cross-bar for converting between
types in one namespace and types in another, it made the file about twice as
long.
It's quite lucky, really, that I encountered this bug. If I hadn't been primed
to look out for compiler bugs, I might have failed to spot the second one, which
is with std::auto_ptr. I covered smart pointers recently, in this post:
http://ego.istic.org/articles/Shallow%20copy%20constructors%20are%20broken.pod,
but I didn't mention std::auto_ptr. This class is just a pointer whose
destructor deletes the pointed-to object, and with a copy constructor that
does a move instead of a copy (that is, it clears the old pointer to prevent
double-freeing). Use it when you want to heap-allocate an object and keep
exactly one pointer to it, which can be passed around. When the pointer to it
goes out of scope, the object is freed. If you just want the auto-delete
functionality without passing the pointer around, then it is clearer to use
boost::scoped_ptr instead, which is similar but disallows copying.
std::auto_ptr can't be used in STL containers because of its unusual copy
semantics.
The MSVS attempt at std::auto_ptr can't really be called an implementation
because it has bugs that break standard-compliance, in the area of implicit
conversions. Imagine you have a class hierarchy, and want to convert between
pointers to derived and base classes.
class A { ... };
class B { ... };
class C : public A, public B { ... };
class D : public C { ... };
C *c = new C();
D *d = new D();
B *b = c; // valid and equivalent to static_cast<B*>(c);
A *a = d; // valid and equivalent to static_cast<A*>(d);
This is all fine and dandy, apart from the fact that after all this pointer
duplication I have no idea which ones I should deallocate. There is a bit of
fiddling around behind the scenes when I convert a C* to a B*, because the
compiler has to "jump over" the A data in *c to get at the B data, but
it all works. std::auto_ptr and other smart pointers declare extra implicit
conversion operators so that they work in the same way:
std::auto_ptr<C> c(new C()); std::auto_ptr<D> d(new D()); std::auto_ptr<B> b(c); // works as above, but now c is a null pointer std::auto_ptr<A> a(d); // works as above, but now d is a null pointer // When b and a go out of scope, they will be deleted. I hope the author of A // and B remembered to make their destructors virtual!
Now I don't have to remember to delete the objects by hand, and if there is
some exception, they will be deleted anyway, without needing a catch(...)
block and rethrow. That is, until I use Microsoft's C++ standard library. It
erroneously uses void * internally, so the first conversion above generates
an invalid pointer to B (because it fails to do the pointer-jiggling I
mentioned in the previous paragraph), and the second conversion fails at
compile-time (it fails to notice that A and D are related). It also allows
several implicit conversions from pointers to unrelated things to
std::auto_ptr, conversions disallowed by ISO because attempting to do them is
almost certainly a sign of driver error.
In the same area of cross-bar code, I was allocating objects of various types,
sticking the pointer in a std::auto_ptr, fiddling with the fields of these
objects (in ways that might give rise to exceptions), and each time passing the
std::auto_ptr to a common function which wanted a pointer to their common
ancestor class (corresponding to A in the example), and which would either
call release() on it, sticking the resulting pointer into a large data
structure, or would let it go out of scope and be deallocated. Because it
wouldn't let me do the conversion, I couldn't use std::auto_ptrs in this
case, which meant I had to call delete myself, and worry about adding
catch(...) blocks (with rethrows) in exactly the right places to avoid memory
leaks in the case of exceptions. Again, the code-for-the-standards-impaired is
about twice as long as the code-for-actual-C++-compilers, and is error-prone and
less maintainable.
As I said, both of these bugs are known and Microsoft is treating them as solved because they are not present in Visual Studio 2008. My employer pays quite a fee for MSDN licenses for Visual Studio, and we (and other people bitten by this bug) have been left in the lurch by Microsoft's premature abandonment of a product we can't migrate away from (for compatibility reasons). This is one of many reasons I won't use Visual Studio for my recreational programming, and it's yet another reminder why letting third-party, closed software vendors lock your users and customers into their product is bad for you and bad for your users.
It's so hard to see the Sun with the truth in your eyes.
Comments on Visual Studio sucks | no comments | Post a comment