Dec 17, 2019
Avoiding Common Problems with Smart Pointers¶
Introduction¶
Incorrectly passing allocators when creating or resetting smart pointers
(bslma::ManagedPtr
and bsl::shared_ptr
) is the most common mistake made
when using those pointers.
We have added checks to bde_verify
to detect patterns of misuse and we have
created utility methods to encapsulate passing allocators properly when
creating smart pointer objects.
We recommend using the utility methods
bslma::ManagedPtrUtil::makeManaged
bslma::ManagedPtrUtil::allocateManaged
bsl::make_shared
bsl::allocate_shared
to create smart pointer objects instead of doing so “by hand”.
Creating a Managed Pointer¶
In full-blown usage, the creation of a managed pointer can involve three mentions of an allocator. A typical such case might be -
void foo(bslma::Allocator *theAllocator)
{
// ...
bslma::ManagedPtr<bsl::string> smp( // 0
new (*theAllocator) // 1
bsl::string(100, 'x', theAllocator), // 2
theAllocator); // 3
// ...
}
We discuss each appearance of the allocator below in understanding smart pointer construction.
Possibilities for Error¶
Whenever the same thing needs to be repeated multiple times, there is always the possibility of error.
In the case of managed pointer usage, all three allocator arguments are optional, so erroneous usage may include leaving out arguments that should have been supplied, causing the default allocator instead of the supplied allocator to be used. And since the supplied allocator very often is the default allocator, such misuse can go unnoticed for a long time.
bde_verify
Checks¶
As of version 1.3.12, bde_verify
contains a check named managed-pointer
which attempts to identify possible errors in smart pointer construction
(for both ManagedPtr
and shared_ptr
, despite the name of the check) and
reset. They’re described in more
detail below, but in summary,
the’re tagged MP01
, MP02
, … and they detect mismatched allocator use.
To run only this check, use
bde_verify -cl='all off' -cl='check managed-pointer on' files...
See the bde_verify
documentation
for more detail.
Avoid Errors by Using Utilities¶
BDE 3.44 provides new utility methods for creating managed pointers.
Similar utility methods for shared pointers previously existed. These methods
encapsulate object creation and the passing of allocators so that the
allocator argument does not need to be repeated, The methods come in pairs,
a make
variant that uses the default allocator and an allocate
variant that
takes a specified allocator.
The example above becomes -
bslma::ManagedPtr<bsl::string> smp =
bslma::ManagedPtrUtil::allocateManaged<bsl::string>(
theAllocator, 100, 'x');
If we were creating a shared pointer instead of a managed pointer, we would say -
bsl::shared_ptr<bsl::string> ssp =
bsl::allocate_shared<bsl::string>(theAllocator, 100, 'x');
Note that we pass the allocator first, followed by the arguments to the object constructor. The utility functions query traits of the object type to determine whether the object uses allocators, and if so, forward the allocator to the constructor of the object as the last argument.
If we want to use the default allocator, we can say -
bslma::ManagedPtr<bsl::string> smp =
bslma::ManagedPtrUtil::makeManaged<bsl::string>(100, 'x');
bsl::shared_ptr<bsl::string> ssp = bsl::make_shared<bsl::string>(100, 'x');
Understanding Smart Pointer Construction and Its Discontents¶
The Allocator Arguments¶
At line 0 we create a smart pointer to string.
At line 1, we issue the new
expression to allocate the string
that will be held by the managed pointer, passing it the
placement argument (*theAllocator)
so that it will use the
specified allocator to allocate memory for the string.
At line 2, we pass the allocator to the string constructor so that the string will use that allocator for its internal memory allocation; the fact that the string itself was allocated using a particular allocator does not automatically forward that allocator to the string. Without this argument, the string would use the default allocator for its internal allocations.
Finally, at line 3 we pass the allocator to the managed pointer
constructor to use as the means for deallocating the held string.
Deallocation must match allocation; since memory for the held string
came from theAllocator
(at line 1), we must use theAllocator
to
free that memory.
Consequences of Omission or Mismatch¶
Leaving out the allocator at line 2, or using a different one, is the least pernicious. It results in the string using a different allocator from the one used to allocate the string. This is not an error per se, but it may lead to inefficiency by failing to take advantage of the presumably more performant supplied allocator.
Leaving out the placement argument at line 1 or the deleter argument
at line 3 is the most troublesome, since it implies that the wrong
method will be used to deallocate memory. However, the default allocator,
unless reset, allocates and frees memory using operator new
and
operator delete
, so omitting one of line 1 and line 3 can still result in
code that “works” until a non-default allocator is supplied.
And obviously, actual mismatches between line 1 and line 3 will cause errors if the deleter attempts to free memory in a way inconsistent with how the allocator supplied it.
Details of the bde_verify
managed-ptr
Check¶
Examples of the output it produces follow -
file.cpp:28:22: warning: MP01: Shared pointer without deleter
will use 'operator delete'
ManagedPtr<char> mp02(new (*pa) char);
^ ~~~~~~~~~~~~~~
file.cpp:72:22: warning: MP02: Different allocator and deleter
for shared pointer
ManagedPtr<char> ep03(new (ta) char, ba);
^ ~~ ~~
file.cpp:70:22: warning: MP03: Deleter provided for non-placement
allocation for shared pointer
ManagedPtr<char> ep01(new char, da);
^ ~~~~~~~~ ~~
bde_verify
attempts to detect if an allocator variable has been initialized
to the default allocator explicitly, and if so issues a gentler warning, off
by default -
file.cpp:30:22: warning: MPOK01: Shared pointer without deleter
using default-initialized allocator variable
ManagedPtr<char> mp04(new (*da) char);
^ ~~~
file.cpp:18:27: note: MPOK01: Initialization is here
bslma::Allocator *da = bslma::Default::allocator();
^