Rule A18-1-4 (required, implementation, automated)

A pointer pointing to an element of an array of objects shall not be passed to a smart pointer of single object type.

Rationale

A dynamically allocated array of objects needs a corresponding deallocation function, e.g. allocation by new[] requires deallocation by delete[], see also rule A18-5-3 in section 6.18.5. Smart pointers of a single object type, e.g. std::unique_ptr and std::shared_ptr, by default have a deleter associated with them which is only capable of deleting a single object. Therefore, it is undefined behavior if a pointer pointing to an element of an array of objects is passed to such a

smart pointer. With the standard library smart pointer templates std::unique_ptr and std::shared_ptr, this is possible when calling the constructor or the reset function. Note that the standard library provides a specialization of the std::unique_ptr template for array types, std::unique_ptr<T[]>, and corresponding overloads for std::make_unique. Usage of these features is conforming to this rule. Note that corresponding features for std::shared_ptr are only available in C++17 (usage of std::shared_ptr<T[]> with C++11 and C++14 will lead to compilation errors). The overloads for std::make_shared will only be available in C++20. Furthermore, note that it is possible to create a smart pointer of single object type with a custom deleter handling an array of objects. This is well behaving as long as this smart pointer is actually managing an array of objects. However, such a use is error-prone, since the smart pointer can be assigned a single object again in the reset function; it may no longer be possible in C++17 (moving a std::unique_ptr<T[]> into a std::shared_ptr is no longer allowed); and it is superseded by better alternatives in C++17 (availability of std:shared_ptr<T[]>). Therefore such usage is considered not compliant to this rule. In many cases, using a container such as std::array or std::vector or a smart pointer to a container, e.g. std::shared_ptr<std::vector>, is a better alternative than a smart pointer to an array of objects.

Example

// $Id: A18-1-4.cpp 313638 2018-03-26 15:34:51Z jan.babst $
#include <memory>
class A
{
};
void F1()
{
// Create a dynamically allocated array of 10 objects of type A.
auto up1 = std::make_unique<A[]>(10); // Compliant

std::unique_ptr<A> up2{up1.release()}; // Non-compliant
}
void F2()
{
auto up1 = std::make_unique<A[]>(10); // Compliant

std::unique_ptr<A> up2;
up2.reset(up1.release()); // Non-compliant
}
void F3()
{
auto up = std::make_unique<A[]>(10); // Compliant

std::shared_ptr<A> sp{up.release()}; // Non-compliant
}

void F4()
{
auto up = std::make_unique<A[]>(10); // Compliant

std::shared_ptr<A> sp;
sp.reset(up.release()); // Non-compliant
}
void F5()
{
auto up = std::make_unique<A[]>(10); // Compliant

// sp will obtain its deleter from up, so the array will be correctly
// deallocated. However, this is no longer allowed in C++17.
std::shared_ptr<A> sp{std::move(up)}; // Non-compliant
sp.reset(new A{});
// leads to undefined behavior
}
void F6()
{
auto up = std::make_unique<A[]>(10); // Compliant

// Well behaving, but error-prone
std::shared_ptr<A> sp{up.release(),
std::default_delete<A[]>{}};
sp.reset(new A{}); // leads to undefined behavior
}

// Non-compliant

See also

HIC++ v4.0 [9]: 17.3.4: Do not create smart pointers of array type. ISO/IEC 14882:2014 [3]: 20.8 Smart pointers: [smartptr] Rule A18-5-3 in section 6.18.5