Re: C++ sucks for games

From: Alex Drummond (a.drummond_at_ucl.ac.uk)
Date: 10/27/04


Date: Wed, 27 Oct 2004 14:51:55 +0000


> you could just as easily create nightmare typo scenarios in any
> language, including plain english. that example is plainly a careless
> mistake. no one and no language can promise that some idiot won't come
> along and do something stupid to muck everything up.

Of course, but GC still improves the situation by reducing the number of
mistakes it is possible for a programmer to make (although of course memory
management bugs are still possible in a GC'd language). Just because GC is
not a pancea doesn't mean it isn't useful.

> oh yes, but by following some simple guidelines they can be just as
> easily avoided. i honestly cannot remember the last time i had a
> dangling reference crop up in my code.

OK, but what if you're using pointers instead of references (which you have
to some of the time in any real world C++ program). The whole issue of
references and memory management is a bit of a red herring here. IIRC it
started from a misunderstanding when I (rather stupidly) used "reference"
as a general term covering C++ pointers /and/ references. Whatever the
merrits of C++ references (and IMHO it is quite easy to create dangling
references as a novice C++ programmer, it took me a while to stop making
that mistake), you can't do all your memory management using copying and
(C++) reference passing.

> but let's say for argument's sake that you did have to have a shared
> object and that you could not be sure of it's lifetime.
> std::tr1::shared_ptr (aka boost::shared_ptr).

Yes, but reference counting smart pointers are less efficient than good GC
(assuming you are using them in a significant/performance critical portion
of your program) and you have to use the clumsy template syntax in C++ (if
only it had type inference...) I remember once trying to implement a parser
combinator library in C++ (without realising that Boost's Spirit library
had got there first). I gave up when the types for a smart pointer to a
instantiated template of a parser functor got to be about 1.5 lines long.
It became increasingly difficult to work out where the actual code was,
since it was burried in irrelavent memory management details. Memory
management is a solved problem, rather like repeatedly executing an
expression a given number of times is a solved problem, leading to the
development of languages with various looping abstractions. I don't want to
have to resolve the problem of memory management in every program I write.

> but c++ is more suited to a more deterministic
> problem domain. vaguely allocating and tossing memory around with no
> concept of ownership or responsibility isn't a good idea in tighter
> architectures, or even in more expansive architectures where you want
> close control of resources. if memory is cheap and plentiful, then sure,
> but then c++ may not be the best tool for the job.

I realise I'm making a little more or your comment here than is strictly
speaking fair, but given that memory /is/ cheap and plentiful on modern
computers, would that not mean that C++ is not usually the best tool for
the job for modern application design?

As for "vaguely allocating and tossing memory around with no concept of
ownership and responsibility", this is sometimes necessary. The concept of
a particular module/class/bit of a program "owning" a region of memory is
not always a particularly natural one. C++ forces you to use this concept
to make memory management manageable, but sometimes it doesn't make much
sense to treat memory as a "resource" managed by a particular part of the
program. This is when you need to start using smart pointers (/smart/
pointers: auto_ptr is helpful, but the memory management problem it solves
is not exactly thorny -- just add a few delete statements at the end of
your function). To reiterate, smart (reference counting) pointers as a
general approach to memory management are quite inefficient (i.e. they are
less efficient than a good GC) and a pain to use (i.e. they are less easy
to use than automatic memory management). Thus any C++ program which has a
lot of complicated memory management to do is quite likely to run no faster
than it would had it been written in a GC'd language, and be significantly
harder to understand.

This isn't just Lisp propaganda. For example take a look at the rationale
for Boehm GC at http://www.hpl.hp.com/personal/Hans_Boehm/gc/issues.html.
One quote:

"In general, it is considerably easier to implement and use sophisticated
data structures in the presence of a garbage collector. As a result, it is
easier to avoid imposing arbitrary limits on program parameters (e.g.
string lengths), and to ensure that performance scales reasonably for large
inputs. ****** The cord string package included with our garbage collector
is an example of a useful abstraction that could not be provided without
some form of garbage collection, and would be more expensive with
user-provided reference counting, or the like ******. A program based on
something like the cord package has some chance of continuing to run
reasonably if it receives a 10 megabyte input string where the programmer
expected a line of text. This is much less likely with a string package
that uses a conventional representation."

(****** emphasis is mine, of course)

Alex

Mark A. Gibbs wrote:

>
> Hannah Schroeter wrote:
>
>> Or you're just doing a typo-like mistake, as in
>>
>> class A {
>> public:
>> explicit A(const std::string& x) : x(x) {}
>>
>> protected:
>> const std::string& x;
>> // ^ oops, intention was the same w/o the &
>> };
>>
>> A* make_a()
>> {
>> return (new A("foo"));
>> // oops, temporary std::string gets destroyed before we return,
>> // the A object contains a dangling reference
>> }
>>
>> int main()
>> {
>> std::auto_ptr<A> x(make_a());
>> // do something with x
>> }
>>
>> Of course it was just a typo. Just a typo that can be horrendous to
>> debug.
>
> you could just as easily create nightmare typo scenarios in any
> language, including plain english. that example is plainly a careless
> mistake. no one and no language can promise that some idiot won't come
> along and do something stupid to muck everything up.
>
> incidently, make_a() should probably return an auto_ptr<A>.
>
>>>references cannot cause memory leaks.
>>
>> ^ add "alone"
>>
>> No, but dangling references are easily built.
>
> oh yes, but by following some simple guidelines they can be just as
> easily avoided. i honestly cannot remember the last time i had a
> dangling reference crop up in my code.
>
>>>in good design, memory is freed by the same entity that allocated it.
>>>the use of pointers or references does not invalidate good design. it
>>>allows you to if you so choose, and sometimes that's a valid design
>>>decision. but if your memory management is scattered and out of control,
>>>that's your fault, not any language's.
>>
>>
>> So if you need to pass around things, like
>>
>> A B
>> is created (doesn't exist yet)
>> creates x
>>
>> is created
>> ---- pass on x ->
>> now needs the x
>> is destroyed
>> lives on
>>
>> you have to agree on a discipline of managing x, or you copy it over,
>> so A destroys its copy and B manages its own copy. The latter may not
>> always be viable, because things might rely on the identity of x.
>
> i said good design. this is horrendous design.
>
> to put it in concrete terms:
>
> Oven Furnace
> is built (doesn't exist yet)
> contains it's own thermostat
> is created
> ----- give thermostat to furnace --->
> now needs the thermostat
> is destroyed
> lives on
> (with the oven's thermostat)
>
> does that make logical sense?
>
> furnace would be free to put the thermostat into any kind of state that
> may be invalid within the context of oven, or vice-versa (you set the
> oven to 275° and your house ignites).
>
> but let's say for argument's sake that you did have to have a shared
> object and that you could not be sure of it's lifetime.
> std::tr1::shared_ptr (aka boost::shared_ptr).
>
>> If the pattern how x is transferred or not transferred, you can't just
>> say "ok, A creates x, B destroys it always".
>
> again, shared_ptr. but c++ is more suited to a more deterministic
> problem domain. vaguely allocating and tossing memory around with no
> concept of ownership or responsibility isn't a good idea in tighter
> architectures, or even in more expansive architectures where you want
> close control of resources. if memory is cheap and plentiful, then sure,
> but then c++ may not be the best tool for the job.
>
>> auto_ptr isn't a smart pointer. It's quite dumbass, not even viable
>> for containers at all.
>
> of course auto_ptr is a smart pointer, it's just a smart pointer with a
> very specific problem domain. it's specifically for the purpose of
> transfer of ownership (as i mention above, c++ tends towards more
> deterministic programs).
>
> "smart pointer" is a family of solutions, not a specific solution.
>
> boost::scoped_ptr has an even narrower problem domain, but i still find
> it occasionally useful - although auto_ptr does the same job and more at
> about the same cost.
>
>>>besides, with a garbage collected-only language, you can't practically
>>>implement raii, and i think that's a serious flaw.
>>
>>
>> You don't need raii then, as most raii is about memory, and the rest
>> can be handled with good macro facilities (see Common Lisp's
>> with-open-file for an example) or higher order functions.
>
> memory is only a very, very small part of raii. the first "r" is
> resource - and resource can be anything at all. in fact, in my code, i
> probably use it most often for synchronization.
>
> no matter what happens, i can be assured that all resources will be
> properly released. that is, for any possible code path, including those
> that i cannot predict, and including all exceptional code paths (short
> of an absolute emergency (ie. crash)), the resource will be properly
> cleaned up. and i can know *exactly* when that will happen.
>
> that is not trivial to do, no matter what macros or other tricks you
> use. i don't know lisp that well, but i have a hard time seeing this
> pattern being easily implemented in any garbage collected environment.
> the only way you can do it in java is if you use try-finally blocks
> everywhere, and that's just grotesque.
>
> indi



Relevant Pages

  • Re: C++ Garbage Collector on VMS?
    ... worry about memory management in a virtual memory OS design. ... The Language and the Language Run-Time is generally left holding the proverbial bag, if the bag isn't passed directly to the programmer. ...
    (comp.os.vms)
  • Re: Delphi and the .Net platform
    ... memory management: running in a gc'd vm is completely different. ... All the mechanisms in the language for memory deallocation are ... namespaces: These are logical groupings of code that should not ...
    (borland.public.delphi.non-technical)
  • Re: Java equivalent of C++ Spirit or Boost Graph Library ?
    ... garbage collected language interpreter in a non-collected language. ... programmers doing a lot of the work that would be considered memory ... If we're talking about memory management at the C++ level, ... A reasonable-performance garbage collector would require that the ...
    (comp.lang.java.programmer)
  • Re: Javascript memory leaks?
    ... If Java-like memory management systems would exclude memory leaking as ... A memory leak occurs then Garbage Collector cannot mark ... there is at least one reference left ...
    (comp.lang.javascript)
  • Re: Lock-free reference counting
    ... the counters are _NOT_ scattered in memory... ... adjacent in memory to the object they count. ... reference counts are _NEVER_ bumped. ... "Reference counting is frequently chosen as an automatic memory management ...
    (comp.programming)