After experimenting with various options, I decided to write the code in an interrupt-based version. It uses an internal timer of the Arduino to call an interrupt routine every N microseconds. The interrupt routine just increments a variable for how many time it has been called, and in addition if a new value is ready for output, it pulses -LDAC to make it take effect. This means you can do the calculations for the next value to output without needing to worry about timing, then send the new value to the DAC ready for the next timing interrupt.
Lots of code follows. It will generate square, sawtooth, triangle and sine wave output. It's pretty limited: square waves only go to 10kHz reliably, and the other waveforms to much lower frequencies, the exact value depending on the number of samples you ask for. Too many samples and some will get skipped, or you get timing jitter. The sine wave is the worst, though it could be considerably improved by getting the values from a lookup table rather than the sin() function. If you want to see how to do this much better than I did, take a look at http://www.instructables.com/id/Arduino-Waveform-Generator/?ALLSTEPS, a really nice piece of work. I also used this code as an excuse for coding closer to the hardware, for example setting output pins by writing to the ports rather than using the digitalWrite() function. I also made some use of types such as unsigned long to get better precision in the calculations without resorting to floating point calculation, and unsigned int for many of the other variables. You have to be quite careful when coding this way, and use casts to avoid truncating values early.
(Code formatted using http://hilite.me/. It does a nice job. I'm not sure I like including code as opposed to giving a download link - opinions welcome, should anyone actually be reading this :-))