

생 포인터인지 검사하는 것은 std 라이브러리에서 가능하다.
스마트 포인터인지도 검사 가능하게 만들어보자.

1
2
3
4
5
6
|
int main()
{
static_assert(woon2::is_unique_ptr_v<std::unique_ptr<int>>);
static_assert(woon2::is_shared_ptr_v<std::shared_ptr<int>>);
static_assert(woon2::is_smart_ptr_v<std::unique_ptr<int>>);
}
|
cs |


예제 코드
// ==========================================================================
// Test Code
// ==========================================================================
#include <iostream>
#include "smart_pointer_type_trait.hpp"
template < typename Ty >
struct Sptr : std::shared_ptr< Ty >
{
Sptr( const Ty& val ) : std::shared_ptr< Ty >( new Ty{ val } ) {}
};
template < typename Ty >
struct Uptr : std::unique_ptr< Ty >
{
Uptr( const Ty& val ) : std::unique_ptr< Ty >( new Ty{ val } ) {}
};
int main()
{
auto check = []( auto&& target )
{
std::cout << typeid( target ).name() << '\n';
std::cout << "is_shared_ptr: " << woon2::is_shared_ptr_v< decltype( target ) > << '\n';
std::cout << "is_unique_ptr: " << woon2::is_unique_ptr_v< decltype( target ) > << '\n';
std::cout << "is_smart_ptr: " << woon2::is_smart_ptr_v< decltype( target ) > << '\n';
std::cout << "is_shared_ptr_soft: " << woon2::is_shared_ptr_soft_v< decltype( target ) > << '\n';
std::cout << "is_unique_ptr_soft: " << woon2::is_unique_ptr_soft_v< decltype( target ) > << '\n';
std::cout << "is_smart_ptr_soft: " << woon2::is_smart_ptr_soft_v< decltype( target ) > << '\n';
std::cout << "is_pointable: " << woon2::is_pointable_v< decltype( target ) > << '\n';
std::cout << "\n\n\n";
};
const auto a = std::make_unique< int >( 3 );
check( a );
Uptr< int > b{ 2 };
check( b );
auto c = std::make_shared< int >( 4 );
check( c );
Sptr< int > d{ 5 };
check( d );
int e = 8;
check( e );
int* f = &e;
check( f );
}
|
cs |


// ==========================================================================
// Implementation: easily changable dynamic allocation policy.
// Just retype the pointer type in line 93.
// ==========================================================================
#include <iostream>
#include "smart_pointer_type_trait.hpp"
template < typename Ptr >
decltype( auto ) get_raw_pointer( Ptr&& ptr )
{
if constexpr ( std::is_pointer_v< std::decay_t< Ptr > > ) return std::forward< Ptr >( ptr );
else return ptr.operator->();
}
// ======================================================================
// a wrapper class of a pointer, for safe compilation.
// if the wrapped class doesn't have the required method,
// try to behave like the expected behavior from the name.
// if it fails, it eventually does nothing. ( or static assertion fails. )
// you can get the original pointer type by using unwrapped ( pointer< Ty >::unwrapped ).
// ======================================================================
template < typename Ty, typename Ptr >
class pointer_impl
{
public:
using unwrapped = Ptr;
void reset( Ty* target = nullptr )
{
static_assert( woon2::has_reset_v< Ptr > || std::is_pointer_v< Ptr >,
"pointer_impl< Ty, Ptr >::reset(): Ptr doesn't have reset()." );
if constexpr ( woon2::has_reset_v< Ptr > ) impl.reset( target );
else
{
delete impl;
impl = target;
}
}
auto release()
{
static_assert( woon2::has_release_v< Ptr > || std::is_pointer_v< Ptr > || woon2::is_shared_ptr_soft_v< Ptr >,
"pointer_impl< Ty, Ptr >::release(): Ptr doesn't have release()." );
if constexpr ( woon2::has_release_v< Ptr > ) return impl.release();
else
{
auto ret = impl;
reset();
return ret;
}
}
decltype( auto ) get_deleter() noexcept
{
if constexpr ( woon2::has_get_deleter_v< Ptr > ) return impl.get_deleter();
else return nullptr;
}
decltype( auto ) get_deleter() const noexcept
{
if constexpr ( woon2::has_get_deleter_v< Ptr > ) return impl.get_deleter();
else return nullptr;
}
Ty* get() noexcept
{
return operator->();
}
const Ty* get() const noexcept
{
return operator->();
}
Ty& operator*() noexcept { return *impl; }
const Ty& operator*() const noexcept { return *impl; }
Ty* operator->() noexcept { return get_raw_pointer( impl ); }
const Ty* operator->() const noexcept { return get_raw_pointer( impl ); }
operator bool() const noexcept { return static_cast<bool>( get_raw_pointer( impl ) ); }
// special member functions
pointer_impl( Ty* impl = nullptr ) : impl{ impl } {}
private:
Ptr impl;
};
// pointer_impl end =====================================================
// ======================================================================
// **********************************************************************
// Decoupling by using keyword.
// Memory allocation policy has the only dependancy on this code.
// ======================================================================
template < typename Ty >
using pointer = pointer_impl< Ty, std::unique_ptr< Ty > >; // can change to diffrent pointer
// if you add more pointer class,
// you can also change to the pointer class.
// **********************************************************************
// ======================================================================
template < typename Ty, typename ... Args >
auto make( Args&& ... args )
{
return pointer< Ty >{ new Ty{ std::forward< Args >( args )... } };
}
struct INT
{
INT( int impl = 0 ) : impl{ impl } { std::cout << "INT constructor called\n"; }
~INT() { std::cout << "INT destructor called\n"; }
INT( const INT& other ) : impl{ other.impl } { std::cout << "INT copy constructor called\n"; }
INT& operator=( const INT& other )
{
if ( this != &other )
{
std::cout << "INT copy allocator called\n";
impl = other.impl;
}
return *this;
}
INT( INT&& other ) noexcept : impl{ other.impl } { std::cout << "INT move constructor called\n"; }
INT& operator=( INT&& other ) noexcept
{
if ( this != &other )
{
std::cout << "INT move allocator called\n";
impl = other.impl;
}
return *this;
}
operator int() { return impl; }
int impl;
};
int main()
{
std::cout << "must call constructor ==================\n";
auto a = make< INT >( 4 );
std::cout << "========================================\n\n\n";
std::cout << "get_deleter() call =====================\n";
std::cout << typeid( a.get_deleter() ).name() << '\n';
std::cout << "========================================\n\n\n";
std::cout << "print value ============================\n";
std::cout << "value: " << *a << '\n';
std::cout << "========================================\n\n\n";
std::cout << "must call constructor and destructor ===\n";
a.reset( new INT{ 5 } );
std::cout << "========================================\n\n\n";
std::cout << "print value ============================\n";
std::cout << "value: " << *a << '\n';
std::cout << "========================================\n\n\n";
std::cout << "must call destructor ===================\n";
if constexpr ( std::is_pointer_v< decltype( a )::unwrapped > )
{
std::cout << "cause unwrapped pointer type was a raw pointer, called release().\n";
a.release();
}
}
|
cs |

// ==========================================================================
// Implementation: added new pointer class
// this is an example of smart_pointer_type_trait.hpp's additional type trait guide.
// ==========================================================================
#include <iostream>
#include "smart_pointer_type_trait.hpp"
template< typename Ty >
class value_pointer
{
public:
// methods
void reset( const Ty& val = Ty{} )
{
if ( !raw_ptr ) raw_ptr = new Ty{ val };
else *raw_ptr = val;
}
void reset( Ty&& val )
{
if ( !raw_ptr ) raw_ptr = new Ty{ std::move( val ) };
else *raw_ptr = std::move_if_noexcept( val );
}
void release()
{
delete raw_ptr;
raw_ptr = nullptr;
}
void swap( value_pointer& right )
{
if ( !raw_ptr ) throw std::exception{ "value_pointer< Ty >::swap(): raw_ptr was null pointer." };
if ( !right.raw_ptr ) throw std::exception{ "value_pointer< Ty >::swap(): invalid argument( right was null pointer. )" };
auto temp = std::move_if_noexcept( *right.raw_ptr );
*right.raw_ptr = std::move_if_noexcept( *raw_ptr );
*raw_ptr = std::move_if_noexcept( temp );
}
// operators
Ty& operator*() { return *raw_ptr; }
const Ty& operator*() const { return *raw_ptr; }
Ty* operator->() { return raw_ptr; }
const Ty* operator->() const { return raw_ptr; }
// special member functions
value_pointer( const Ty& val ) : raw_ptr{ new Ty{ val } } {}
value_pointer( Ty&& val ) : raw_ptr{ new Ty{ std::move( val ) } } {}
value_pointer( const value_pointer& other ) : raw_ptr{ new Ty{ *other } } {}
value_pointer& operator=( const value_pointer& other )
{
if ( this != &other ) reset( *other );
return *this;
}
value_pointer( value_pointer&& other ) noexcept : raw_ptr{ new Ty{ std::move( *other ) } } {}
value_pointer& operator=( value_pointer&& other ) noexcept
{
if ( this != &other ) reset( std::move( *other ) );
return *this;
}
~value_pointer() { if ( raw_ptr ) delete raw_ptr; }
private:
Ty* raw_ptr;
};
template < typename Ty >
struct Vptr : value_pointer< Ty > {};
// =====================================================================
// *********************************************************************
// new type trait ( value_ptr )
// just retyped the pointer's name.
// *********************************************************************
// =====================================================================
namespace woon2
{
namespace detail
{
template < typename T >
struct is_value_pointer_impl : std::false_type {};
template < typename T >
struct is_value_pointer_impl< value_pointer< T > > : std::true_type {};
template < typename T >
std::true_type is_value_pointer_soft_impl( value_pointer< T >* );
std::false_type is_value_pointer_soft_impl( ... );
}
template < typename T >
using is_value_pointer = detail::is_value_pointer_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool is_value_pointer_v = is_value_pointer< T >::value;
template < typename T >
using is_value_pointer_soft = decltype( detail::is_value_pointer_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
template < typename T >
constexpr bool is_value_pointer_soft_v = is_value_pointer_soft< T >::value;
}
// =====================================================================
struct INT
{
INT( int impl = 0 ) : impl{ impl } { std::cout << "INT constructor called\n"; }
~INT() { std::cout << "INT destructor called\n"; }
INT( const INT& other ) : impl{ other.impl } { std::cout << "INT copy constructor called\n"; }
INT& operator=( const INT& other )
{
if ( this != &other )
{
std::cout << "INT copy allocator called\n";
impl = other.impl;
}
return *this;
}
INT( INT&& other ) noexcept : impl{ other.impl } { std::cout << "INT move constructor called\n"; }
INT& operator=( INT&& other ) noexcept
{
if ( this != &other )
{
std::cout << "INT move allocator called\n";
impl = other.impl;
}
return *this;
}
operator int() { return impl; }
int impl;
};
int main()
{
// test type traits ==============================================
static_assert( woon2::is_pointable_v< value_pointer< int > >, "is_pointable trait doesn't work." );
static_assert( woon2::is_value_pointer_v< value_pointer< int > >, "is_value_pointer trait doesn't work." );
static_assert( woon2::is_value_pointer_soft_v< Vptr< int > >, "is_value_pointer_soft trait doesn't work." );
static_assert( !woon2::has_get_deleter_v< value_pointer< int > >, "has_get_deleter trait doesn't work." );
static_assert( woon2::has_release_v< value_pointer< int > >, "has_release trait doesn't work." );
static_assert( woon2::has_swap_v< value_pointer< int > >, "has_swap trait doesn't work." );
static_assert( woon2::has_reset_v< value_pointer< int > >, "has_reset trait doesn't work." );
// ===============================================================
std::cout << "====================================================\n";
std::cout << "value_pointer test\n";
std::cout << "====================================================\n\n\n";
try {
std::cout << "construct a ========================================\n";
value_pointer< INT > a{ 3 };
std::cout << "====================================================\n\n\n";
std::cout << "construct b ========================================\n";
value_pointer< INT > b{ 4 };
std::cout << "====================================================\n\n\n";
std::cout << "print values =======================================\n";
std::cout << "a: " << *a << ", b: " << *b << '\n';
std::cout << "====================================================\n\n\n";
std::cout << "swap a, b ==========================================\n";
a.swap( b );
std::cout << "====================================================\n\n\n";
std::cout << "print values =======================================\n";
std::cout << "a: " << *a << ", b: " << *b << '\n';
std::cout << "====================================================\n\n\n";
std::cout << "release a ==========================================\n";
a.release();
std::cout << "====================================================\n\n\n";
std::cout << "reset a with b =====================================\n";
a.reset( *b );
std::cout << "====================================================\n\n\n";
std::cout << "print values =======================================\n";
std::cout << "a: " << *a << ", b: " << *b << '\n';
std::cout << "====================================================\n\n\n";
std::cout << "must call destructors ==============================\n";
}
catch ( const std::exception& e )
{
std::cout << e.what();
exit( -1 );
}
}
|
cs |


활용 방안
개발 중 unique_ptr를 사용할지, shared_ptr를 사용할지, 다른 RAII 객체를 사용할지, 아니면 아예 생 포인터를 사용할지 선택이 많이 오간다.
따라서 나중에 선택을 바꾸더라도 상관 없도록 타입 체크를 통해 컴파일 될 모든 코드들을 생성시켜 놓는다.
동적 할당 함수나, 포인터를 통해 상호작용하는 어떠한 함수(기왕이면 메모리 내용 자체를 건드리는) 등에 static_assertion, if constexpr 등과 같이 적용할 수 있을 것이다.

코드
https://github.com/MyeongWoonJang/SmartPointerTypeTrait/tree/main
GitHub - MyeongWoonJang/SmartPointerTypeTrait: C++ Type Traits for Smart Pointer
C++ Type Traits for Smart Pointer. Contribute to MyeongWoonJang/SmartPointerTypeTrait development by creating an account on GitHub.
github.com
// ==========================================================================
// Type Traits for smart pointers
// Supporting is_unique_ptr< T >, is_shared_ptr< T >, is_smart_ptr< T >, is_ptr< T >
// Usage is like std::is_pointer< T >.
// Soft version type traits additionally evalute a derived class from a pointer as true.
// Just write "soft" after a type_trait to detect inheritance too.
// ==========================================================================
#ifndef _smart_pointer_type_trait
#define _smart_pointer_type_trait
#include <memory>
namespace woon2
{
namespace detail
{
// for removing const, volatile, reference.
// via this, type traits have no dependency on qualifiers.
template < typename T >
using remove_cvr_t = std::remove_cv_t< std::remove_reference_t< T > >;
}
// =======================================================================================
// shared_ptr type trait
// =======================================================================================
namespace detail
{
template < typename T >
struct is_shared_ptr_impl : std::false_type {};
template < typename T >
struct is_shared_ptr_impl< std::shared_ptr< T > > : std::true_type {};
template < typename T >
std::true_type is_shared_ptr_soft_impl( const std::shared_ptr< T >* );
std::false_type is_shared_ptr_soft_impl( ... );
}
template < typename T >
using is_shared_ptr = detail::is_shared_ptr_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool is_shared_ptr_v = is_shared_ptr< T >::value;
template < typename T >
using is_shared_ptr_soft = decltype( detail::is_shared_ptr_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
template < typename T >
constexpr bool is_shared_ptr_soft_v = is_shared_ptr_soft< T >::value;
// shared_ptr type trait end =============================================================
// =======================================================================================
// unique_ptr type trait
// =======================================================================================
namespace detail
{
template < typename T >
struct is_unique_ptr_impl : std::false_type {};
template < typename T, typename Dx >
struct is_unique_ptr_impl< std::unique_ptr< T, Dx > > : std::true_type {};
template < typename T, typename Dx >
std::true_type is_unique_ptr_soft_impl( std::unique_ptr< T, Dx >* );
std::false_type is_unique_ptr_soft_impl( ... );
}
template < typename T >
using is_unique_ptr = detail::is_unique_ptr_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool is_unique_ptr_v = is_unique_ptr< T >::value;
template < typename T >
using is_unique_ptr_soft = decltype( detail::is_unique_ptr_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
template < typename T >
constexpr bool is_unique_ptr_soft_v = is_unique_ptr_soft< T >::value;
// unique_ptr type trait end =============================================================
// =======================================================================================
// united type trait ( smart pointers )
// =======================================================================================
template < typename T >
using is_smart_ptr = std::conditional_t<
is_shared_ptr_v< T >,
std::true_type,
std::conditional_t<
is_unique_ptr_v< T >,
std::true_type,
std::false_type
>
>;
template < typename T >
constexpr bool is_smart_ptr_v = is_smart_ptr< T >::value;
namespace detail
{
template < typename T, typename Dx >
std::true_type is_smart_ptr_soft_impl( std::unique_ptr< T, Dx >* );
template < typename T >
std::true_type is_smart_ptr_soft_impl( std::shared_ptr< T >* );
std::false_type is_smart_ptr_soft_impl( ... );
}
template < typename T >
using is_smart_ptr_soft = decltype( detail::is_smart_ptr_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
template < typename T >
constexpr bool is_smart_ptr_soft_v = is_smart_ptr_soft< T >::value;
// united type trait ( smart pointers ) end ==============================================
// =======================================================================================
// united type trait ( all pointable classes )
// =======================================================================================
namespace detail
{
template < typename T, typename _ = void >
struct is_pointable_impl : std::false_type {};
template < typename T >
struct is_pointable_impl< T* > : std::true_type {};
template < typename ... Ts >
struct is_pointable_helper {};
template < typename T >
struct is_pointable_impl< T,
std::conditional_t<
false,
is_pointable_helper<
decltype( std::declval< T >().operator->() ),
decltype( std::declval< T >().operator*() )
>,
void
>
> : std::true_type {};
}
template < typename T >
using is_pointable = detail::is_pointable_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool is_pointable_v = is_pointable< T >::value;
// united type trait ( all pointable classes ) end =======================================
// method trait ( trace if a pointer class has a specified method. ) =====================
namespace detail
{
// has_reset detail ==================================================================
template < typename T, typename _ = void >
struct has_reset_impl : std::false_type {};
template < typename ... Ts >
struct has_reset_helper {};
template < typename T >
struct has_reset_impl< T, std::conditional_t<
false,
has_reset_helper<
decltype( std::declval< T >().reset() )
>,
void
>
> : std::true_type {};
// has_reset detail end ==============================================================
// has_release detail ================================================================
template < typename T, typename _ = void >
struct has_release_impl : std::false_type {};
template < typename ... Ts >
struct has_release_helper {};
template < typename T >
struct has_release_impl< T, std::conditional_t<
false,
has_release_helper<
decltype( std::declval< T >().release() )
>,
void
>
> : std::true_type {};
// has_release detail end ============================================================
// has_get_deleter detail ============================================================
template < typename T, typename _ = void >
struct has_get_deleter_impl : std::false_type {};
template < typename ... Ts >
struct has_get_deleter_helper {};
template < typename T >
struct has_get_deleter_impl< T, std::conditional_t<
false,
has_get_deleter_helper<
decltype( std::declval< T >().get_deleter() ),
decltype( std::declval< T >().get_deleter() )
>,
void
>
> : std::true_type {};
// has_get_deleter detail end=========================================================
// has_swap detail ===================================================================
template < typename T, typename _ = void >
struct has_swap_impl : std::false_type {};
template < typename ... Ts >
struct has_swap_helper {};
template < typename T >
struct has_swap_impl< T, std::conditional_t<
false,
has_swap_helper<
decltype( std::declval< T >().swap( std::declval< std::add_lvalue_reference_t< T > >() ) )
>,
void
>
> : std::true_type {};
// has_swap detail end ===============================================================
} // detail
template < typename T >
using has_reset = detail::has_reset_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool has_reset_v = has_reset< T >::value;
template < typename T >
using has_release = detail::has_release_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool has_release_v = has_release< T >::value;
template < typename T >
using has_get_deleter = detail::has_get_deleter_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool has_get_deleter_v = has_get_deleter< T >::value;
template < typename T >
using has_swap = detail::has_swap_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool has_swap_v = has_swap< T >::value;
// method trait end ======================================================================
// =======================================================================================
// Additional pointer type trait
// You can add your own pointer class's type trait.
// This is the template for that.
// Can be detected by is_pointable< T >
// =======================================================================================
// just for sample, it will be better for you to make your pointer class be in another header file.
template < typename T >
struct your_pointer {};
// /sample
// just alter all "your_pointer"s to your pointer class's name.
namespace detail
{
template < typename T >
struct is_your_pointer_impl : std::false_type {};
template < typename T >
struct is_your_pointer_impl< your_pointer< T > > : std::true_type {};
template < typename T >
std::true_type is_your_pointer_soft_impl( your_pointer< T >* );
std::false_type is_your_pointer_soft_impl( ... );
}
template < typename T >
using is_your_pointer = detail::is_your_pointer_impl< detail::remove_cvr_t< T > >;
template < typename T >
constexpr bool is_your_pointer_v = is_your_pointer< T >::value;
template < typename T >
using is_your_pointer_soft = decltype( detail::is_your_pointer_soft_impl( std::declval< detail::remove_cvr_t< T >* >() ) );
template < typename T >
constexpr bool is_your_pointer_soft_v = is_your_pointer_soft< T >::value;
// additional pointer type trait end =============================================================
}
#endif // _smart_pointer_type_trait
|
cs |

참고 자료
https://stackoverflow.com/questions/65752626/concept-for-smart-pointers
Concept for smart pointers
Given a class template like this: template<typename T> struct foo { T data; }; How may one determine whether T is a smart pointer such as std::shared_ptr<T_underlying> using C++20
stackoverflow.com
씹어먹는 C++ - <16 - 3. 타입을 알려주는 키워드 decltype 와 친구 std::declval>
modoocode.com
https://en.cppreference.com/w/cpp/types/conditional
std::conditional - cppreference.com
template< bool B, class T, class F > struct conditional; (since C++11) Provides member typedef type, which is defined as T if B is true at compile time, or as F if B is false. The behavior of a program that adds specializations for conditional is undefined
en.cppreference.com
https://stackoverflow.com/questions/12042824/how-to-write-a-type-trait-is-container-or-is-vector
How to write a type trait `is_container` or `is_vector`?
Is it possible to write a type trait whose value is true for all common STL structures (e.g., vector, set, map, ...)? To get started, I'd like to write a type trait that is true for a vector and f...
stackoverflow.com
https://en.cppreference.com/w/cpp/language/sfinae
SFINAE - cppreference.com
"Substitution Failure Is Not An Error" This rule applies during overload resolution of function templates: When substituting the explicitly specified or deduced type for the template parameter fails, the specialization is discarded from the overload set in
en.cppreference.com

'Short Coding' 카테고리의 다른 글
C++ std::string 메모리 관리 포함 거의 모든 멤버함수까지 직접 구현하기 (0) | 2023.01.12 |
---|