Detect or avoid dead references to temporary on compile time

517 views Asked by At

The following minimal-ish program segfaults when compiling with -O3 and perhaps with -O2, but executes fine with -O0 (with clang 4.0):

#include <iostream>

class A {
public:
  virtual void me() const { std::cerr << "hi!\n"; }
};

class B {
public:
  B(const A& a_) : a(a_) {}
  virtual void me() const { a.me(); }

private:
  const A& a;
};

class C {
public:
  C(const B& b_) : b(b_) {}
  void me() const { b.me(); }

public:
  const B& b;
};

int main() {
  C c = C(A());
  c.me();
}

The reason is that c.b is initialized with a reference to a temporary object of class B that is constructed from a temporary A. After the constructor c.C() exits, the temporary B is gone but the reference to it remains in c.b.

What good practices can I employ to avoid this situation, given that I don't control the implementation of B or A? Are there static analyzers that are able to detect this condition? (My version of scan-build did not find the problem.)

Related: Detect dangling references to temporary

2

There are 2 answers

3
rodrigo On BEST ANSWER

I personally don't like having member variables as references. They cannot be copied or moved, and the user of the class must ensure that the object outlives the reference itself. An exception might be an internal auxiliary class designed to be used as temporary only objects, such as functors.

Replacing the reference with a pointer should be straightforward, and then if you also use a pointer in the constructor:

class C {
public:
  C(const A *a_) : a(a_) {}
private:
  const A *a;
};

...and so on. If the class is very big and you feel lazy and don't want to change the member, you can just change the constructor:

class C {
public:
  C(const A *a_) : a(*a_) {}
private:
  const A &a;
};

In order to misuse this class, as the OP says, you'll have to write something like:

C c = C(&A());

And then the error should be obvious: taking a pointer to a temporary is a very bad idea!

PS: I would add an explicit to your constructor, just to avoid it from participating in automatic conversions, which, I think, are part of your problem.

1
Bathsheba On

I would derive separate classes from B and C (possibly even using a template class).

These classes would contain a non-reference member which becomes the thing that a and b refer to.

I'd then implement the necessary copy constructors / assignment operators in these derived classes to prevent dangling references.

(Then I'd have a robust conversation with the author of B and C).