...
In this noncompliant code example, a custom container class uses an allocator object to obtain storage for arbitrary element types. While the CopyElementscopy_elements()
function is presumed to call copy constructors for elements being moved into the newly-allocated storage, this example fails to explicitly call the default constructor for any additional elements being reserved. If such an element is accessed through the operator[]()
function, it results in undefined behavior, depending on the type T
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <memory> template <typename T, typename Alloc = std::allocator<T>> class Container { T *UnderlyingStorageunderlyingStorage; size_t NumElementsnumElements; void CopyElementscopy_elements(T *Fromfrom, T *Toto, size_t Countcount); public: void reserve(size_t Countcount) { if (Countcount > NumElementsnumElements) { Alloc Aalloc; T *Pp = Aalloc.allocate(Countcount); // Throws on failure try { CopyElementscopy_elements(UnderlyingStorageunderlyingStorage, Pp, NumElementsnumElements); } catch (...) { Aalloc.deallocate(Pp, Countcount); throw; } UnderlyingStorageunderlyingStorage = Pp; } NumElementsnumElements = Countcount; } T &operator[](size_t Idxidx) { return UnderlyingStorageunderlyingStorage[Idxidx]; } const T &operator[](size_t Idxidx) const { return UnderlyingStorageunderlyingStorage[Idxidx]; } }; |
Compliant Solution
...
Code Block | ||||
---|---|---|---|---|
| ||||
#include <memory> template <typename T, typename Alloc = std::allocator<T>> class Container { T *UnderlyingStorageunderlyingStorage; size_t NumElementsnumElements; void CopyElementscopy_elements(T *Fromfrom, T *Toto, size_t Countcount); public: void reserve(size_t Countcount) { if (Countcount > NumElementsnumElements) { Alloc Aalloc; T *Pp = Aalloc.allocate(Countcount); // Throws on failure try { CopyElementscopy_elements(UnderlyingStorageunderlyingStorage, Pp, NumElementsnumElements); for (size_t i = NumElementsnumElements; i < Countcount; ++i) { Aalloc.construct(&Pp[i]); } } catch (...) { Aalloc.deallocate(Pp, Countcount); throw; } UnderlyingStorageunderlyingStorage = Pp; } NumElementsnumElements = Countcount; } T &operator[](size_t Idxidx) { return UnderlyingStorageunderlyingStorage[Idxidx]; } const T &operator[](size_t Idxidx) const { return UnderlyingStorageunderlyingStorage[Idxidx]; } }; |
Exceptions
MEM53-CPP-EX1: If the object is trivially constructable, it need not have its constructor explicitly called to initiate the object's lifetime. If the object is trivially destructible, it need not have its destructor explicitly called to terminate the object's lifetime. These properties can be tested by calling std::is_trivially_constructible()
and std::is_trivially_destructible()
from <type_traits>
. For instance, integral types such as int
and long long
do not require an explicit constructor or destructor call.
...