On 18.07.2018 15:14,
stayp...@gmail.com wrote:
>
> I would like to create a class/template but want to prevent at
> compile time where it can live. Seems like type_traits or the
> upcoming concepts cannot be use for that.
>
> I have two scenarios:
> - Enforce at compile time that an object can only be instanciate on
> the stack. This is to implement a "safe" dynarray type like object.
Since you mention elsewhere that the /purpose/ is to use stack
allocation like a VLA or alloca, there is as far as I know no portable
way to differentiate whether an object is allocated in automatic memory,
as a static variable, or as part of an array or class type object that
in turn can be allocated anywhere.
Semi-portably you might compare the object's address to an address
obtained in `main`, assuming that the stack has some maximum size (that
you might infer from a preprocessor symbol) and that it grows down in
memory (again probably best to let the build commands specify).
That approach gives you runtime checking.
As a convenience measure, getting some ungood usage errors up front at
compile time, I would then "= delete" the class' allocation functions,
`operator new` and `operator new[]`.
> - Enforce at compile tiem that an object can only be instanciate on
> the heap. This is to ensure That no "Big" object are instanciated > on the stack.
I would make the destructor non-public, i.e. implement the restriction
with one or two lines:
protected:
virtual ~Dynamic_only() {}
That works nicely with e.g. `std::make_shared`, at the cost of having to
use a simple wrapper function. It doesn't work quite so nicely with
`std::make_unique`, requiring per-class boilerplate code. But doable, as
exemplified below.
The `std::make_shared` support below, the `std::make_shared` wrapper,
can easily be moved to a reusable mix-in class, I think.
Note: the FAQ instead recommends making the constructors non-public and
defining factory functions. In C++03 that wasn't so practical, requiring
one factory function per constructor. And so I argued a bit with
Marshall Cline (the FAQ maintainer at the time), to change the FAQ to
recommend the non-public destructor approach, but to no avail.
However, with C++11 we now have ~perfect argument forwarding, so that
the umpteen factory functions of C++03 can be rolled into one per class,
so that the FAQ's advice isn't so impractical anymore. :) So that's an
alternative. However I like being able to use the usual instantiation
code instead of calling factory functions.
----------------------------------------------------------------------
#include <memory> // std::make_shared
#include <type_traits> // std::is_base_
#include <utility> // std::(enable_if_t, forward)
using namespace std;
template< class T > using ptr_ = T*;
template< class T > using ref_ = T&;
class Dynamic_only
{
protected:
virtual ~Dynamic_only() {}
public:
Dynamic_only() {}
Dynamic_only( const int, const int, const int ) {}
// A default unique_ptr doesn't store a unique deleter, so one must
// use indirection via a specialization of std::default_delete. This
// works for direct use of this class, but a derived class needs to
// duplicate the effort. std::unique_ptr is just deleter-challenged,
// which is a cost of zero overhead for the default instantiation.
static void destroy( ptr_<Dynamic_only> p ) { delete p; }
// A default shared_ptr stores a unique deleter obtained at the point
// where the shared_ptr is constructed, e.g. in the return stm below:
template< class... Args >
static auto make_shared( Args&&... args )
-> shared_ptr<Dynamic_only>
{
struct Accessible: Dynamic_only
{
using Dynamic_only::Dynamic_only; // Constructors.
~Accessible() override {}; // Just to be explicit.
};
return std::make_shared<Accessible>( forward<Args>( args )... );
}
};
#ifndef TEST_UNSUPPORTED_MAKE_UNIQUE
// Annoyingly verbose support for std::unique_ptr:
namespace std
{
template<>
struct default_delete<Dynamic_only>
{
constexpr default_delete()
noexcept
{}
template< class U
, class = enable_if_t< is_convertible_v< ptr_< U >, ptr_<
Dynamic_only > > >
>
default_delete( ref_<const default_delete<U>> )
noexcept
{}
void operator()( const ptr_<Dynamic_only> p ) const
{
Dynamic_only::destroy( p );
}
};
} // namespace std
#endif
auto main()
-> int
{
const auto up1 = make_unique<Dynamic_only>();
const auto up2 = make_unique<Dynamic_only>( 1, 2, 3 );
const auto sp1 = Dynamic_only::make_shared();
const auto sp2 = Dynamic_only::make_shared( 1, 2, 3 );
(void) up1, up2, sp1, sp2;
#ifdef TEST_STACK_ALLOC
Dynamic_only o; //! Doesn't
compile.
(void) o;
#endif
#ifdef TEST_UNSUPPORTED_MAKE_UNIQUE
const auto up = std::make_unique<Dynamic_only>(); //! Doesn't
compile.
(void) up;
#endif
#ifdef TEST_STD_MAKE_SHARED
const auto sp = std::make_shared<Dynamic_only>(); //! Doesn't
compile.
(void) sp;
#endif
}
----------------------------------------------------------------------
> I guess being able to enforce/prevent being global could be usefull. Example an
> object you don't want global because its constructure depends on some stuff
> initialised in the main().
>
> Does this idea make any kind of sense?
Yes, but it's an example of an issue that perhaps is better solved with
just a code /comment/.
Less work. :)
> I can see it being complicated to implement with scenarios that
> might be impossible detect at compile time. Maybe it's a feature more suited
> to a static analyser?
Not sure. Maybe.
Cheers!,
- Alf