Main Content

CERT C++: OOP50-CPP

Do not invoke virtual functions from constructors or destructors

Since R2021a

Description

Rule Definition

Do not invoke virtual functions from constructors or destructors.1

Polyspace Implementation

The rule checker checks for Virtual function call from constructors and destructors.

Examples

expand all

Issue

Virtual function call from constructors and destructors occurs when you invoke virtual functions in a constructor or a destructor with possibly unexpected results.

When you call virtual functions in the constructor or destructor of a class in a hierarchy, it is not clear which instance of the virtual function you intend to invoke. Calls to virtual functions in a constructor or a destructor resolves to the implementation of the virtual function in the currently executing class instead of the most derived override.

There are two cases where calling virtual functions from a constructor or destructor does not raise this defect.

  • When you use the explicitly qualified ID to call the virtual function. Consider this code:

    Base(){
    	Base::foo();
    }
    The call to Base::foo uses the explicitly qualified ID of the function. This call is compliant with this rule because it explicitly states that the implementation of foo belonging to Base is invoked.

  • When the you specify the called virtual function as final in the currently executing class. Consider this code:

    Base(){
    	foo();
    }
    //...
    void foo() override final{
    //...
    }
    In this case, a call to foo implies a call to Base::foo because the function is specified as the final override.

Risk

When you call a virtual function, you expect the compiler to resolve the call to the most derived override of the virtual function at runtime. Unlike in other functions, virtual function calls in constructors and destructors are resolved differently, resulting in unexpected behavior. Invoking virtual functions in constructor and destructors might cause undefined behavior, resulting in a memory leak and security vulnerabilities. Consider this code:

#include <iostream>

class Base
{
public:
	Base() { foo(); }  //Noncompliant
	~Base(){bar();}    //Noncompliant
	virtual void foo() {
		std::cout<<"Base Constructor\n";
	}
	virtual void bar(){
		std::cout<<"Base Destructor\n";
	}

};
class Derived : public Base
{
public:
	Derived() : Base() {}
	~Derived() = default;
	virtual void foo() {
		std::cout<<"Derived constructor\n"; 
	}
	virtual void bar() {
		std::cout<<"Derived Constructor\n"; 
	}
};
int main(){
	Derived d;
	return 1;
}

The constructor of d calls the constructor for Base class, which invokes the virtual function foo. Because the derived class is not constructed yet, the compiler cannot invoke Derived::foo. Only the function Base::foo() is invoked. Similarly, when the virtual function bar is invoked in the destructor of Base, the derived class Derived is already destroyed. The compiler cannot invoke Derived::bar. Only the function Base::bar is invoked. The output of this code is:

Base Constructor
Base Destructor
instead of:
Base Constructor
Derived constructor
Derived Constructor
Base Destructor
The portion of d belonging to the class Derived is neither allocated nor deallocated. This behavior might result in memory leaks or security vulnerabilities.

Fix

To fix this issue, avoid calling virtual functions in constructors and destructors. For typical constructor or destructor tasks such as memory allocation and deallocation, initialization, or message logging, use functions that are specific to each class in a hierarchy.

Example — Class-Specific Memory Management
#include <iostream>

class Base {
public:	
	Base()
	{
		allocator();  //Noncompliant 
	}
	virtual ~Base()
	{
		deallocator();  //Noncompliant 
	}

	virtual void allocator(){
	    //...
	}
	virtual void deallocator(){
	    //...
	}
};

class Derived : public Base {
public:
	Derived() : Base() {}
	virtual ~Derived() = default;
protected:
	void allocator() override
	{
		Base::allocator();
		// Get derived resources...
	}
	void deallocator() override
	{
		// Release derived resources...
		Base::deallocator();
	}
};

int main(){
	Derived dObj;
	//...
	return 1;
}

In this example, the code attempts class-specific memory management by implementing the functions allocator and deallocator as virtual. A call to these functions does not resolve to the most derived override.

  • During the construction of the Derived object dObj, only the function Base::allocator() is invoked. Because the Derived class is not constructed yet, the function Derived::allocator is not invoked.

  • During the destruction of dObj, only the function Base::deallocator is invoked because the class Derived is already destroyed.

Because of the use of virtual functions in the constructor and destructor of dObj, the Derived portion of dObj is neither allocated nor deallocated. This behavior is unexpected and might lead to memory leaks and security vulnerabilities.

Correction — Class-Specific Memory Management

One possible correction is to use class-specific nonvirtual functions for tasks that are commonly performed in constructors and destructors. In this code, allocation and deallocation tasks are performed by class-specific nonvirtual functions.

#include <iostream>

class Base {
public:	
	Base()
	{
		allocator_base(); 
	}
	virtual ~Base()
	{
		deallocator_base();
	}
protected:
	void allocator_base(){
		// Allocate base resources
	}
	void deallocator_base(){
		// Deallocate base resources
	}
};

class Derived : public Base {
public:
	Derived(){
		allocator_derived();
	}
	virtual ~Derived(){
		deallocator_derived();
	}
protected:
	void allocator_derived()
	{
		// Allocate derived resources...
	}
	void deallocator_derived()
	{
		// Deallocate derived resources...
	}
};

int main(){
	Derived dObj;
	//...
	return 1;
}

Check Information

Group: Rule 09. Object Oriented Programming (OOP)

Version History

Introduced in R2021a


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.