The pointer-to-member operators .* and ->* are used to obtain an object or function as though it were a member of an underlying object. For instance, the following are functionally equivalent ways to call the member function f() on the object o:
struct S {
void f() {}
};
void func() {
S o;
void (S::*pm)() = &S::f;
o.f();
(o.*pm)();
} |
The call of the form o.f() uses class member access at compile time to look up the address of the function S::f() on the object o. The call of the form (o.*pm)() uses the pointer-to-member operator .* to call the function at the address specified by pm. In both cases, the object o is the implicit this object within the member function S::f().
The C++ Standard, [expr.mptr.oper], paragraph 4, states [ISO/IEC 14882-2014]:
Abbreviating pm-expression.*cast-expression as
E1.*E2,E1is called the object expression. If the dynamic type ofE1does not contain the member to whichE2refers, the behavior is undefined.
(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.)
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.
In this noncompliant code example, a pointer-to-member object is obtained from D::g, but upcast to be a B::*. When called on an object whose dynamic type is D, the pointer-to-member call is well-defined. However, in this noncompliant code example, the dynamic type of the underlying object is B, resulting in undefined behavior:
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)();
}
|
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:
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)();
}
|
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
OOP39-CPP | High | Probable | High | P6 | L2 |
Tool | Version | Checker | Description |
Search for other vulnerabilities resulting from the violation of this rule on the CERT website.
|
| [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)