Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Restricting a class to dynamic instantiation via destructor access -- what about smart pointers?

22 views
Skip to first unread message

Alf P. Steinbach

unread,
Dec 23, 2016, 10:00:55 PM12/23/16
to
For a restriction to dynamic allocation by making the destructor
non-`public`, I wonder what would be a good way to support destructor
invocation via `std::shared_ptr` and other smart pointers?

[code]
#include <type_traits>
#include <iostream>
using namespace std;

#define STATIC_ASSERT( e ) static_assert( e, "`" #e "` <-- must be true" )

namespace cppx{
template< class Type >
constexpr auto is_destructible()
-> bool
{ return std::is_destructible<Type>::value; }
} // namespace cppx

template< class Derived >
struct Dynamic_only
{
void selfdestroy() { delete this; }

virtual ~Dynamic_only() {}

Dynamic_only()
{
using cppx::is_destructible;
STATIC_ASSERT( not is_destructible<Derived>() );
}
};

struct S: Dynamic_only<S> {};
class T: public Dynamic_only<T> { private: ~T(){} };
class U: public Dynamic_only<U>{ protected: ~U(){} };

auto main() -> int
{
#ifdef TEST_INSTANTIATION
new S{};
new T{};
new U{};
#endif
cout << boolalpha;
cout << "S -> " << is_destructible<S>::value << "\n"; // true
cout << "T -> " << is_destructible<T>::value << "\n"; // false
cout << "U -> " << is_destructible<U>::value << "\n"; // false
}
[/code]


Cheers!,

- Alf

Paavo Helde

unread,
Dec 27, 2016, 1:03:37 PM12/27/16
to
On 24.12.2016 5:00, Alf P. Steinbach wrote:
> For a restriction to dynamic allocation by making the destructor
> non-`public`, I wonder what would be a good way to support destructor
> invocation via `std::shared_ptr` and other smart pointers?
>
> [code]
> #include <type_traits>
> #include <iostream>
> using namespace std;
>
> #define STATIC_ASSERT( e ) static_assert( e, "`" #e "` <-- must be true" )
>
> namespace cppx{
> template< class Type >
> constexpr auto is_destructible()
> -> bool
> { return std::is_destructible<Type>::value; }
> } // namespace cppx
>
> template< class Derived >
> struct Dynamic_only
> {
> void selfdestroy() { delete this; }

I have had similar desires in the past to demand only dynamic allocation
for some classes and I went to great lengths to develop my own
smartpointers which supported that. However, by now I have come to
conclusion that these desires were futile and actually not needed for
anything.

First note that internal refcounting cannot be made work with
std::shared_ptr in principle, as std::shared_ptr also supports
thread-safe weak pointers, but such weak pointers are not compatible
with internal reference counters (a race condition between weak pointer
mechanisms and the object destructor).

So, if one wants to use std::shared_ptr, one cannot use internal
reference counting. This actually solves one of the reasons why one
might want to prohibit stack allocation - if there is no selfdestroy()
function then there is no danger that somebody might accidentally call
it and try to delete an object which has not been allocated dynamically.

There remains the danger in that a std::shared_ptr can be constructed
from an unusable raw pointer like 'this' or &x (this practice would work
fine in a legacy codebase using some kind of intrusive refcounting, but
not with std::shared_ptr).

For performance reasons we wanted to use std::make_shared() or
std::allocate_shared() anyway instead of plain new, so there was no
point in allowing to construct a std::shared_ptr() from a raw pointer.
Alas, this constructor is there and will not go away. So, in our
codebase, to mitigate this problem, I created another wrapper pointer
class derived from std::shared_ptr which has the constructor from raw
pointer deleted. All the code uses this class rather than
std::shared_ptr, which basically eliminates the possibility of accidents.

As the end result, one can now create local stack objects of those types
and these work fine, one just cannot obtain a valid smartpointer to such
an object (and creating an invalid smartpointer requires some conscious
mischief).


Öö Tiib

unread,
Dec 28, 2016, 2:36:34 AM12/28/16
to
One can use 'std::enable_shared_from_this' together with
'std::make_shared' to get close to internal reference counter.
The drawback there is that object will be destroyed when shared count
reaches zero but the memory will be deallocated only when weak count
also reaches zero.
0 new messages