It may not be a recommended practice, but AFAIK, it is safe. It is true that in general, taking a pointer to P, casting it to a pointer to Q and using it as a pointer to Q leads to undefined behaviour. Here it looks even worse, because the alignment requirement of char are known to be the weakest possible.
But the char * tBuf pointer has been obtained through a new expression. Such a new expression internally rely on a allocation function to obtain storage, and draft n4296 for c++14 says in 3.7.4.1 Allocation functions [basic.stc.dynamic.allocation] §2:
The allocation function attempts to allocate the requested amount of storage. If it is successful, it shall
return the address of the start of a block of storage whose length in bytes shall be at least as large as
the requested size... The pointer returned shall be suitably aligned so that it can be converted
to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used
to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call
to a corresponding deallocation function).
So this line *(WORD*)tBuf = htons((WORD)numToSend); only does perfectly defined operations:
convert numToSend from an integer type to an unsigned type, and 4.7 Integral conversions [conv.integral] says:
A prvalue of an integer type can be converted to a prvalue of another integer type...
If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source
integer (modulo 2n where n is the number of bits used to represent the unsigned type)
call htons with a WORD or uint16_t as parameter to return a uint16_t or WORD
- converts a pointer obtained by new to a
WORD * and uses that pointer to access the object in the storage allocated
Simply, the value of the first two bytes of the allocated array is now unspecified. More exactly it is the byte representation of the WORD in the particular implementation.
But it is still allowed to access the allocated array as a character array, even if the first bytes now contain a WORD, because it is explicitely allowed per the so called strict aliasing rule 3.10 Lvalues and rvalues [basic.lval] §10 :
If a program attempts to access the stored value of an object through a glvalue of other than one of the
following types the behavior is undefined:
...
(10.8) — a char or unsigned char type.
If the tBuf pointer had not been obtained through a new expression, the only correct way would have been to do a memcpy:
WORD n_numToSend = htons(numToSend);
memcpy(tBuf, &n_numToSend, sizeof(WORD));
As this one is allowed for any pointer provided the storage is big enough, it is what I would call the recommended practice.