Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Added wording for null pointer-to-member values, and another NCCE/CS pair

...

(A pointer-to-member expression of the form E1->*E2 is converted to its equivalent form, (*(E1)).*E2, so use of pointer-to-member expressions of either form behave equivalently in terms of undefined behavior.)

Further, the C++ Standard, [expr.mptr.oper], paragraph 6 states, in part:

If the second operand is the null pointer to member value, the behavior is undefined.

Do not use a pointer-to-member expression where the dynamic type of the first operand does not contain the member to which the second operand refers, including the use of a null pointer-to-member value as the second operand.

Noncompliant Code Example

...

Code Block
bgColor#FFcccc
languagecpp
struct B {
  virtual ~B() = default;
};

struct D : B {
  virtual ~D() = default;
  virtual void g() { /* ... */ }
};

void f() {
  B *b = new B;
 
  // ...
 
  void (B::*gptr)() = static_cast<void(B::*)()>(&D::g);
  (b->*gptr)();
  delete b;
}

Compliant Solution

In this compliant solution, the upcast is removed, rendering the initial code ill-formed. This emphasizes the underlying problem: that B::g() does not exist. This compliant solution assumed the programmer intent was to use the correct dynamic type for the underlying object:

Code Block
bgColor#ccccff
languagecpp
struct B {
  virtual ~B() = default;
};

struct D : B {
  virtual ~D() = default;
  virtual void g() { /* ... */ }
};

void f() {
  B *b = new D; // Corrected dynamic object type
 
  // ...
  void (D::*gptr)() = &D::g; // Removed static_cast  
  (static_cast<D *>(b)->*gptr)();
  delete b;
}

Noncompliant Code Example

In this noncompliant code example, a null pointer-to-member value is passed as the second operand to a pointer-to-member expression, resulting in undefined behavior:

Code Block
bgColor#FFcccc
languagecpp
struct B {
  virtual ~B() = default;
};

struct D : B {
  virtual ~D() = default;
  virtual void g() { /* ... */ }
};
 
static void (D::*gptr)();
void call_memptr(D *ptr) {
  (ptr->*gptr)();
}
 
void f() {
  D *d = new D;
  call_memptr(d);
  delete d;
}

Compliant Solution

In this compliant solution, gptr is properly initialized to a valid pointer-to-member value, instead of the default value of nullptr:

Code Block
bgColor#ccccff
languagecpp
struct B {
  virtual ~B() = default;
};
 
struct D : B {
  virtual ~D() = default;
  virtual void g() { /* ... */ }
};
 
static void (D::*gptr)() = &D::g;
void call_memptr(D *ptr) {
  (ptr->*gptr)();
}
 
void f() {
  D *d = new D;
  call_memptr(d);
  delete d;
}

Risk Assessment

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

OOP39-CPP

High

Probable

High

P6

L2

...

[ISO/IEC 14882-2014]

5.5, "Pointer-to-Member Operators"

 

OOP37-CPP. Write constructor member initializers in the canonical order      013. Object Oriented Programming (OOP)      014. Concurrency (CON)