What you could do is wrap your variables of interest with a family of generic wrappers that would log a stacktrace and the values on each call. Something like (omitting some details):
template <typename T>
class TracingValue
{
private:
T m_Val;
...
void LogStackTrace() {...}
public:
// write
TracingValue& operator= (const T& val) {
LogStackTrace();
m_Val=val;
return *this;
}
// read
operator T () const { return m_Val; }
// "connect" to other values
TracingValue& operator=(const TracingValue &other) {
LogStackTrace();
m_Val = other.m_Val;
std::cout << "id: " << this->Id() << " new value: " << m_Val
<< " from id: " << other.Id() << std::endl;
return *this;
}
};
Logging the stack traces will be slow and may generate too much data, but if you use it sparingly, you might get a better picture what is happening in your software. You can then put breakpoints in the wrapper to catch the modifications when they're happening.
That should work for the trivial cases. In case serialization and other operations are involved, it might need to be refined further.
Tracing value changes and constructions from other wrapped values is possible. See →Ideone for an example:
TracingValue<double> d;
d = 3.;
d = 42.;
double x = d - 2.;
std::cout << x << std::endl;
TracingValue<double> other_d(d);
TracingValue<double> another_d;
another_d = other_d;
outputting
id: 1 constructed with value: 0
id: 1 new value: 3
id: 1 new value: 42
40
id: 2 constructed with value: 42
id: 2 constructed from id: 1
id: 3 constructed with value: 0
id: 3 new value: 42 from id: 2