I didn't need polymorphism for this particular project. But if you want OOP with STL containers, just use boost::shared_ptr (soon to be standard C++ smart pointers).
On the contrary. Pass by reference is wonderful. You should never use pointers unless you need to change what the object points to. Use a const reference (writing foo const& is ingrained into long term C++ programmers) or passing parameters or a regular reference when you need to modify the value. Using references and the STL, C++ almost feels like a garbage collected language. In fact, its entirely possible (and natural) to write an entire program that never uses new/delete or any pointers at all.
The bigger question is, why do you want to do this? I've used the STL a lot, and I have yet to hit a case where an object needed to remove itself from a list. Could you give me an example?
If you know C++ very well, that might be the problem. The STL is a very different paradigm from C or traditional C++ style code. The big thing is that they are very value oriented, in comparison to previous C/C++ containers, which are very object-oriented. I think this is a major hangup for people coming from more traditional techniques. However, STL containers aren't all that different from containers in other languages. I use Python a lot, and the STL seems like a C++ version of Python lists and dictionaries.
I personally started out using the STL on a fairly complicated one person project. Until then, I had no previous experience with the STL. When I started coding, I just did things the way the STL made me, and I found that it worked *great*. The final program had very few bugs, and even though I was doing tons of data structure management, I didn't use new/delete a single time. It did, however, require a very big change from how I had previously programmed.
I don't do interviews, but I'd be wary of people who have problems with C++ syntax. The language is still small enough that you should be able to hold the whole syntax, as well as most of the features in your head. Its rather hard to write code when you're not fluent in the language.
C'mon. Its not nice to put people down. BA's are human beings, just like us, just with different priorities. Hell, if I weren't an Aerospace major, I'd be an English major:)
You still didn't answer my question: What is the compiler supposed to guess for the blank? >>>>>>>>> Didn't I? --> "It doesn't matter what the programmer meant. The type of 'i' is Derived."
Type inference will assign the left-hand variable whatever the type of the expression on the right hand is. It's a simplification of how type inference in languages like OCaml work, anyway. If the programmer wants to make a base pointer to Derived, he can always just do: Base* i = new Derived; For OO-style code, he should probably put type declarations in anyway. However, C++ isn't just for OO style code, and for those cases, type inference allows the benifets of static typing without all the micromanagement of static typing.
From not being able to be sure what "a + b" actually does >>>>>>>> That's a BS argument you always here from the Java folks. You can't overload operator+ for built-in types, so you're *always* sure what a+b does. If a and b are both built-in types, it does an addition. If one isn't a built-in type, you have to go look at the definition to see what it does, or trust that it implements something along the lines of an addition. In this manner, it is no different than: a.addTo(b). Sure, addTo() seems like its adding 'a' to 'b', but is it? You can lie just as easily with named functions as you can with overloaded operators.
2) I'm writing a kernel in C++ right now. The whole runtime is only 200 lines of C. I can get away with that because on the compilers I'm using, the C++ runtime libraries are almost, but not quite, freestanding. 3) Heh. Using C++ for embedded programming too. Of course, its seriously powerful embedded hardware, but C++ is a great language for embedded use. The only place "bloat" enters into the picture is (possibly) with templates. Speed is never a factor --- C++ code is almost always faster than C code. For example, G++ has such a good optimizer, that a class containing a single integer, with overloaded operator +, -, etc, performs just as fast as a plain integer. For data structures, C++ is usually faster (at the cost of a little code bloat) because templates allow comparison functions and what not to be inlined, while C requires indirection through a function ponter.
Not quite sure what you mean by "allow code-generation at compile time," since that's exactly what template functions allow. >>>>>>>>> Yes, it allows this. In a ugly, hackish manner. See Lisp macros for a mechanism that is much cleaner, has no performance disadvantages (C++, of course, would ban code generation at runtime) and is much easier to use. Also, read up on the metacode extension proposal. Heck, even Alexanderscu admitted he was rather fond of Scheme macros on comp.lang.c++:)
The existing language already permits things awfully close. Local anonymous classes with static functions, for example, come very close to local functions. Also, check out the Boost lambda library. >>>>>>>> Local anonymous classes are still too "out of line" for my taste. One of the nice things about lambdas is that they save you quite a bit of typing. Declaring a local class requires far too much boilerplate to be a useful replacement for real lambdas. And the BLL is cool, but isn't really usable thanks to the hidiously complicated error messages it causes the compiler to generate. The error message bears zero resemblance to the actual code, and is essentially an AST written in particularly ugly template syntax.
____ i = new Derived; (Hint: no matter what you guess, the programmer really meant "the other one".) Now, how is a compiler supposed to guess, if you can't? >>>>>>>>> It doesn't matter what the programmer meant. The type of 'i' is Derived. Type inference isn't complicated. The type of the object is whatever the type of the right hand side expression is. Also, type inference isn't terribly useful for OO code. Where pointers to base types are common. However, C++ is a multi-paradigm language, and type inference makes dealing with code that focuses mostly on value types. The STL is very value-type oriented, and if you follow the style it naturally engenders, you'll end up writing code that may not use operator new or polymorphism or inheritence a single type.
Your solution doesn't solve anything that COM or something similar doesn't. It only deals with method invocation. In particular, you're only dealing with interfaces and ignoring value semantics. For example, say I have a class Foo in library A. In the first version, Foo's private data consists of two integers. Now I derive a class, Bar, from Foo. Bar adds another integer to its private data. So now, sizeof(Foo) == sizeof(int) * 2, and sizeof(Bar) = sizeof(int) * 3. Now, what if Foo grows by an integer in the next version? Now, we've got sizeof(Foo) == sizeof(int) * 3, and sizeof(Bar) == sizeof(int) * 3. Now, if the client code creates an object of type Bar, it will create an object that doesn't have enough space for both Foo and Bar. There is the basic problem. C's memory model fundementally assumes that the layout of structures in memory doesn't change, to the point where it access all data members directly, does all sorts of inlining, etc. The only reason that stuff like COM works is because the client code doesn't ever inherit from the library code, and the client object is a dumb proxy that has no actual state. Java overcomes this by not assuming a static object layout.
PS> There is a way to overcome the FBC problem, but it has a great deal of overhead. It basically involves treating all base classes like virtual bases. A derived class has a *pointer* to the base class, and instead of the compiler statically resolving methods to the base class implementation, it puts all the methods in the derived class. For methods that the derived class does not override, it simply forwards the request to the base class. This requires making all the methods in the class virtual, as well as adding an overhead of an extra pointer dereference to every access of the base class state. It also requires forbidding the compiler to inline any base class methods. It also requires forbidding the client code from manipulating class objects directly (say, malloc()'ing a byte buffer and using a placement new constructor). Java meets all these requirements, so it probably uses a mechanism very similar to this.
auto would simply resolve to whatever the type of expression on the right hand side was. So:
auto x = foo
would simply be a shortcut for:
typeof(foo) x = foo;
The compiler still has enough information to catch any type errors you could make at compile time. OCaml and Haskell (among others) work this way (with functions and whatnot having type declarations, while most stack variables don't) and in practice, it doesn't seem to be a problem.
A lot of C++ code isn't OO though. I've written entire applications without a single hint of OO, just the oddball imperative/functional soup the STL engenders. Because of the heavy use of value types in STL programming, type inference saves you from having to put in a lot of redundant type declarations.
As for horizontal space, I'm talking about the following:
for(std::vector::iterator i = wordVec.begin(); i!=vec.end(); ++i)
That's way too complicated for a simple loop. I consider the rampant use of typedefs to be something of a hack, only necessary because the type system has too many features without a good way to remove some of the burden of manual type management from the programmer.
A) They don't generate code as fast as native C. For most purposes, the 50-75% they do get (reliably) is good enough, but not for the kind of work I do. B) The memory requirements and runtime requirements make are the kicker. My work involves embedded programming, and my personal hobby is kernels and system-level stuff. I have no desire to get the cmucl runtime to operate freestanding, and there is no way I could squeeze Lisp binaries onto my already overloaded embedded systems.
:) I'm using Boost in my OS kernel right now. Works great. Real testament to the portability of C++. However, the Lambda library leaves much to be desired. Even a small use of lambdas makes compile time shoot through the roof, and its absolutely impossible to debug a compile error, because the compiler spits out every template instantiation in the expression, which basically gives you an AST, only less readable...
Also, I wouldn't push for full type inferencing ala OCaml. Operator overloading makes that very difficult. I'd be happy with an overloaded 'auto' keyword (which was suggested awhile ago on comp.lang.c++), which can be emulated pretty well already with GCC's 'typeof()' extension. The following expression:
#define auto_m(name, init) typeof(init) name = init auto_m(var, vec.begin());
I don't see how that helps either of the examples I gave above, anyway. Normally, I wouldn't bitch about a little issue like that, I'd just go and use std::for_each instead of bothering with explicit iterators. However, C++ doesn't have lambdas, so std::for_each is sometimes a lot more trouble than its worth!
Because its easier to polish a language than to fundementally change language paradigms. The template syntax alone would take as long to put into C# as it did to put into C++. Changing C# from a garbage collected to a manual memory model would be fundementally changing the memory management paradigm.
You shouldn't set smart pointers to null. That could lead to a bug if you accidentally access it afterwords. Just let the destructor do the work, and make sure you don't create a smart pointer whose scope is much larger than it needs to be. Heck, that's good advice even for regular pointers.
The fragile base class problem is only an issue for those using closed source libraries. Sucks to be you if this is the case. There is no way to get rid of the fragile basic class issue without making classes *much* slower and breaking the semantics of the C memory model.
Its only been about 5 years since C++ 98 was standardized. And GCC has been very complient for about a year now, so it took 4 years for complient compilers to come out. That's really not *that* bad, considering that the latest push for complience was driven by the new wave of "Modern C++" techniques, which surfaced relatively recently. Further, a lot of the new proposals use pre-existing solutions (the Boost libraries are a big help here) and all the code that was added for template support seems to have some reusability value. See Vandervoode's comments about his C++ metacode extension, and how relativley easy it was to implement in the EDG C++ front-end, thanks to all the existing infrastructure.
Modern C++ really is a cool language. Its hardly clean, and its a big beast to learn, but (IMHO) it allows a great deal of abstraction without sacrificing much (if any) performance. Personally, I'd like to see the following features in C++ 0x.
1) Metafunctions. Like Lisp macros, they allow code-generation at compile time. They're less flexible, because they don't allow access to the AST, but they're much better than the current template-metaprogramming kludge. 2) Lambdas. Even if we don't get true lambdas, with continuations and closures, but I'd like to see some sort of anonymous functions. The STL desperately requires it. Overall, I'd like to see more functional stuff get into the language. Unlike many of the other features discussed, lambdas and higher order functions really need language-level support to work well. 3) Type inference. There is a proposal to allow a new use of the auto keyword like such: auto x = new int; The compiler will automatically detect that 'x' should be an int*. I've wanted this feature from the minute I saw stuff like: int* i = new int; Its so redundant! I'm surprised that Java (whose simple semantics would make type inference much easier) still makes you do stuff like: foo i = new foo; An additional motiviation is that: vector::iterator i = vec.begin() can be shortened to: auto i = vec.begin(); C++ is seriously eating into the horizontal space, thanks to namespaces and nested typedefs and whatnot, and type inference would go a long way in alleviating some of that pain.
The nice thing about these features is they keep with C++'s philosophy. Most of the complexity here is in the compiler --- there is no overhead in the generated code.
Smart pointers are pretty fast and cheap, because they're reference counted. Basically, there are the following costs:
1) Creating a smart pointer involves an extra heap allocation to allocate a counter. This is necessary because boost's smart pointers (which are the basis of the standard) are non-intrusive --- they don't require giving the target object a special counter. This overhead, can be eliminated by using intersive_ptr, which allows you to put the counter inside the object itself, and provide functions to increment/decrement it. 2) Copying a smart pointer involves an atomic increment of a counter. 3) Having a smart pointer go out of scope involves an atomic decrement of a counter. 4) When the last smart pointer is destructed, an extra heap free is needed to free the counter.
2003 is a great deal better than 2002. The version number of VC++ was only bumped to 7.1 from 7.0, but it was more of a 7.0 to 8.0 increase in compatibility. 7.1 is now on a par with GCC in terms of compatibility.
What's missing?
I didn't need polymorphism for this particular project. But if you want OOP with STL containers, just use boost::shared_ptr (soon to be standard C++ smart pointers).
That would suck. That would introduce different scoping rules for named vs unnamed structures.
On the contrary. Pass by reference is wonderful. You should never use pointers unless you need to change what the object points to. Use a const reference (writing foo const& is ingrained into long term C++ programmers) or passing parameters or a regular reference when you need to modify the value. Using references and the STL, C++ almost feels like a garbage collected language. In fact, its entirely possible (and natural) to write an entire program that never uses new/delete or any pointers at all.
struct foo
:container(&c){}
... define comparison operator...
{
foo(list<foo>& c)
void remove()
{
list<foo>::iterator i;
i = find(container->begin(), container-
>end(), *this);
container->erase(i);
}
list<foo>* container;
};
The bigger question is, why do you want to do this? I've used the STL a lot, and I have yet to hit a case where an object needed to remove itself from a list. Could you give me an example?
If you know C++ very well, that might be the problem. The STL is a very different paradigm from C or traditional C++ style code. The big thing is that they are very value oriented, in comparison to previous C/C++ containers, which are very object-oriented. I think this is a major hangup for people coming from more traditional techniques. However, STL containers aren't all that different from containers in other languages. I use Python a lot, and the STL seems like a C++ version of Python lists and dictionaries.
I personally started out using the STL on a fairly complicated one person project. Until then, I had no previous experience with the STL. When I started coding, I just did things the way the STL made me, and I found that it worked *great*. The final program had very few bugs, and even though I was doing tons of data structure management, I didn't use new/delete a single time. It did, however, require a very big change from how I had previously programmed.
I don't do interviews, but I'd be wary of people who have problems with C++ syntax. The language is still small enough that you should be able to hold the whole syntax, as well as most of the features in your head. Its rather hard to write code when you're not fluent in the language.
C'mon. Its not nice to put people down. BA's are human beings, just like us, just with different priorities. Hell, if I weren't an Aerospace major, I'd be an English major :)
You still didn't answer my question: What is the compiler supposed to guess for the blank?
>>>>>>>>>
Didn't I? -->
"It doesn't matter what the programmer meant. The type of 'i' is Derived."
Type inference will assign the left-hand variable whatever the type of the expression on the right hand is. It's a simplification of how type inference in languages like OCaml work, anyway. If the programmer wants to make a base pointer to Derived, he can always just do:
Base* i = new Derived;
For OO-style code, he should probably put type declarations in anyway. However, C++ isn't just for OO style code, and for those cases, type inference allows the benifets of static typing without all the micromanagement of static typing.
From not being able to be sure what "a + b" actually does
>>>>>>>>
That's a BS argument you always here from the Java folks. You can't overload operator+ for built-in types, so you're *always* sure what a+b does. If a and b are both built-in types, it does an addition. If one isn't a built-in type, you have to go look at the definition to see what it does, or trust that it implements something along the lines of an addition. In this manner, it is no different than:
a.addTo(b).
Sure, addTo() seems like its adding 'a' to 'b', but is it? You can lie just as easily with named functions as you can with overloaded operators.
2) I'm writing a kernel in C++ right now. The whole runtime is only 200 lines of C. I can get away with that because on the compilers I'm using, the C++ runtime libraries are almost, but not quite, freestanding.
3) Heh. Using C++ for embedded programming too. Of course, its seriously powerful embedded hardware, but C++ is a great language for embedded use. The only place "bloat" enters into the picture is (possibly) with templates. Speed is never a factor --- C++ code is almost always faster than C code. For example, G++ has such a good optimizer, that a class containing a single integer, with overloaded operator +, -, etc, performs just as fast as a plain integer. For data structures, C++ is usually faster (at the cost of a little code bloat) because templates allow comparison functions and what not to be inlined, while C requires indirection through a function ponter.
Not quite sure what you mean by "allow code-generation at compile time," since that's exactly what template functions allow. :)
>>>>>>>>>
Yes, it allows this. In a ugly, hackish manner. See Lisp macros for a mechanism that is much cleaner, has no performance disadvantages (C++, of course, would ban code generation at runtime) and is much easier to use. Also, read up on the metacode extension proposal. Heck, even Alexanderscu admitted he was rather fond of Scheme macros on comp.lang.c++
The existing language already permits things awfully close. Local anonymous classes with static functions, for example, come very close to local functions. Also, check out the Boost lambda library.
>>>>>>>>
Local anonymous classes are still too "out of line" for my taste. One of the nice things about lambdas is that they save you quite a bit of typing. Declaring a local class requires far too much boilerplate to be a useful replacement for real lambdas. And the BLL is cool, but isn't really usable thanks to the hidiously complicated error messages it causes the compiler to generate. The error message bears zero resemblance to the actual code, and is essentially an AST written in particularly ugly template syntax.
____ i = new Derived;
(Hint: no matter what you guess, the programmer really meant "the other one".) Now, how is a compiler supposed to guess, if you can't?
>>>>>>>>>
It doesn't matter what the programmer meant. The type of 'i' is Derived. Type inference isn't complicated. The type of the object is whatever the type of the right hand side expression is. Also, type inference isn't terribly useful for OO code. Where pointers to base types are common. However, C++ is a multi-paradigm language, and type inference makes dealing with code that focuses mostly on value types. The STL is very value-type oriented, and if you follow the style it naturally engenders, you'll end up writing code that may not use operator new or polymorphism or inheritence a single type.
Your solution doesn't solve anything that COM or something similar doesn't. It only deals with method invocation. In particular, you're only dealing with interfaces and ignoring value semantics. For example, say I have a class Foo in library A. In the first version, Foo's private data consists of two integers. Now I derive a class, Bar, from Foo. Bar adds another integer to its private data. So now, sizeof(Foo) == sizeof(int) * 2, and sizeof(Bar) = sizeof(int) * 3. Now, what if Foo grows by an integer in the next version? Now, we've got sizeof(Foo) == sizeof(int) * 3, and sizeof(Bar) == sizeof(int) * 3. Now, if the client code creates an object of type Bar, it will create an object that doesn't have enough space for both Foo and Bar. There is the basic problem. C's memory model fundementally assumes that the layout of structures in memory doesn't change, to the point where it access all data members directly, does all sorts of inlining, etc. The only reason that stuff like COM works is because the client code doesn't ever inherit from the library code, and the client object is a dumb proxy that has no actual state. Java overcomes this by not assuming a static object layout.
PS> There is a way to overcome the FBC problem, but it has a great deal of overhead. It basically involves treating all base classes like virtual bases. A derived class has a *pointer* to the base class, and instead of the compiler statically resolving methods to the base class implementation, it puts all the methods in the derived class. For methods that the derived class does not override, it simply forwards the request to the base class. This requires making all the methods in the class virtual, as well as adding an overhead of an extra pointer dereference to every access of the base class state. It also requires forbidding the compiler to inline any base class methods. It also requires forbidding the client code from manipulating class objects directly (say, malloc()'ing a byte buffer and using a placement new constructor). Java meets all these requirements, so it probably uses a mechanism very similar to this.
auto would simply resolve to whatever the type of expression on the right hand side was. So:
auto x = foo
would simply be a shortcut for:
typeof(foo) x = foo;
The compiler still has enough information to catch any type errors you could make at compile time. OCaml and Haskell (among others) work this way (with functions and whatnot having type declarations, while most stack variables don't) and in practice, it doesn't seem to be a problem.
A lot of C++ code isn't OO though. I've written entire applications without a single hint of OO, just the oddball imperative/functional soup the STL engenders. Because of the heavy use of value types in STL programming, type inference saves you from having to put in a lot of redundant type declarations.
As for horizontal space, I'm talking about the following:
for(std::vector::iterator i = wordVec.begin(); i!=vec.end(); ++i)
That's way too complicated for a simple loop. I consider the rampant use of typedefs to be something of a hack, only necessary because the type system has too many features without a good way to remove some of the burden of manual type management from the programmer.
A) They don't generate code as fast as native C. For most purposes, the 50-75% they do get (reliably) is good enough, but not for the kind of work I do.
B) The memory requirements and runtime requirements make are the kicker. My work involves embedded programming, and my personal hobby is kernels and system-level stuff. I have no desire to get the cmucl runtime to operate freestanding, and there is no way I could squeeze Lisp binaries onto my already overloaded embedded systems.
:) I'm using Boost in my OS kernel right now. Works great. Real testament to the portability of C++. However, the Lambda library leaves much to be desired. Even a small use of lambdas makes compile time shoot through the roof, and its absolutely impossible to debug a compile error, because the compiler spits out every template instantiation in the expression, which basically gives you an AST, only less readable...
Also, I wouldn't push for full type inferencing ala OCaml. Operator overloading makes that very difficult. I'd be happy with an overloaded 'auto' keyword (which was suggested awhile ago on comp.lang.c++), which can be emulated pretty well already with GCC's 'typeof()' extension. The following expression:
#define auto_m(name, init) typeof(init) name = init
auto_m(var, vec.begin());
could become:
auto var = vec.begin()
I don't see how that helps either of the examples I gave above, anyway. Normally, I wouldn't bitch about a little issue like that, I'd just go and use std::for_each instead of bothering with explicit iterators. However, C++ doesn't have lambdas, so std::for_each is sometimes a lot more trouble than its worth!
Because its easier to polish a language than to fundementally change language paradigms. The template syntax alone would take as long to put into C# as it did to put into C++. Changing C# from a garbage collected to a manual memory model would be fundementally changing the memory management paradigm.
You shouldn't set smart pointers to null. That could lead to a bug if you accidentally access it afterwords. Just let the destructor do the work, and make sure you don't create a smart pointer whose scope is much larger than it needs to be. Heck, that's good advice even for regular pointers.
The fragile base class problem is only an issue for those using closed source libraries. Sucks to be you if this is the case. There is no way to get rid of the fragile basic class issue without making classes *much* slower and breaking the semantics of the C memory model.
Its only been about 5 years since C++ 98 was standardized. And GCC has been very complient for about a year now, so it took 4 years for complient compilers to come out. That's really not *that* bad, considering that the latest push for complience was driven by the new wave of "Modern C++" techniques, which surfaced relatively recently. Further, a lot of the new proposals use pre-existing solutions (the Boost libraries are a big help here) and all the code that was added for template support seems to have some reusability value. See Vandervoode's comments about his C++ metacode extension, and how relativley easy it was to implement in the EDG C++ front-end, thanks to all the existing infrastructure.
Modern C++ really is a cool language. Its hardly clean, and its a big beast to learn, but (IMHO) it allows a great deal of abstraction without sacrificing much (if any) performance. Personally, I'd like to see the following features in C++ 0x.
1) Metafunctions. Like Lisp macros, they allow code-generation at compile time. They're less flexible, because they don't allow access to the AST, but they're much better than the current template-metaprogramming kludge.
2) Lambdas. Even if we don't get true lambdas, with continuations and closures, but I'd like to see some sort of anonymous functions. The STL desperately requires it. Overall, I'd like to see more functional stuff get into the language. Unlike many of the other features discussed, lambdas and higher order functions really need language-level support to work well.
3) Type inference. There is a proposal to allow a new use of the auto keyword like such:
auto x = new int;
The compiler will automatically detect that 'x' should be an int*. I've wanted this feature from the minute I saw stuff like:
int* i = new int;
Its so redundant! I'm surprised that Java (whose simple semantics would make type inference much easier) still makes you do stuff like:
foo i = new foo;
An additional motiviation is that:
vector::iterator i = vec.begin()
can be shortened to:
auto i = vec.begin();
C++ is seriously eating into the horizontal space, thanks to namespaces and nested typedefs and whatnot, and type inference would go a long way in alleviating some of that pain.
The nice thing about these features is they keep with C++'s philosophy. Most of the complexity here is in the compiler --- there is no overhead in the generated code.
Smart pointers are pretty fast and cheap, because they're reference counted. Basically, there are the following costs:
1) Creating a smart pointer involves an extra heap allocation to allocate a counter. This is necessary because boost's smart pointers (which are the basis of the standard) are non-intrusive --- they don't require giving the target object a special counter. This overhead, can be eliminated by using intersive_ptr, which allows you to put the counter inside the object itself, and provide functions to increment/decrement it.
2) Copying a smart pointer involves an atomic increment of a counter.
3) Having a smart pointer go out of scope involves an atomic decrement of a counter.
4) When the last smart pointer is destructed, an extra heap free is needed to free the counter.
2003 is a great deal better than 2002. The version number of VC++ was only bumped to 7.1 from 7.0, but it was more of a 7.0 to 8.0 increase in compatibility. 7.1 is now on a par with GCC in terms of compatibility.