Rather than the nasty #define hack you mention in the question, a cleaner mechanism is to make the test a friend of the class under test. This allows the test code (and just the test code) access to the privates, whilst protecting them from everything else.

However, it is preferable to test through the public interface. If your class X has a lot of code in the private member functions then it might be worth extracting a new class Y which is used by the implementation of class X. This new class Y can then be tested through its public interface, without exposing its use to the clients of class X.