diff --git a/include/boost/gil/image.hpp b/include/boost/gil/image.hpp index 1ada7c2183..649fdceb1a 100644 --- a/include/boost/gil/image.hpp +++ b/include/boost/gil/image.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -37,7 +38,8 @@ namespace boost { namespace gil { //////////////////////////////////////////////////////////////////////////////////////// template< typename Pixel, bool IsPlanar = false, typename Alloc=std::allocator > -class image { +class image +{ public: #if defined(BOOST_NO_CXX11_ALLOCATOR) using allocator_type = typename Alloc::template rebind::other; @@ -64,14 +66,16 @@ class image { image(const point_t& dimensions, std::size_t alignment=0, const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in) - , _allocated_bytes( 0 ) { + , _allocated_bytes( 0 ) + { allocate_and_default_construct(dimensions); } image(x_coord_t width, y_coord_t height, std::size_t alignment=0, const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in) - , _allocated_bytes( 0 ) { + , _allocated_bytes( 0 ) + { allocate_and_default_construct(point_t(width,height)); } @@ -79,32 +83,53 @@ class image { const Pixel& p_in, std::size_t alignment = 0, const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in) - , _allocated_bytes( 0 ) { + , _allocated_bytes( 0 ) + { allocate_and_fill(dimensions, p_in); } + image(x_coord_t width, y_coord_t height, const Pixel& p_in, std::size_t alignment = 0, const Alloc alloc_in = Alloc()) : _memory(nullptr), _align_in_bytes(alignment), _alloc(alloc_in) - , _allocated_bytes ( 0 ) { + , _allocated_bytes ( 0 ) + { allocate_and_fill(point_t(width,height),p_in); } image(const image& img) : _memory(nullptr), _align_in_bytes(img._align_in_bytes), _alloc(img._alloc) - , _allocated_bytes( img._allocated_bytes ) { + , _allocated_bytes( img._allocated_bytes ) + { allocate_and_copy(img.dimensions(),img._view); } template image(const image& img) : _memory(nullptr), _align_in_bytes(img._align_in_bytes), _alloc(img._alloc) - , _allocated_bytes( img._allocated_bytes ) { + , _allocated_bytes( img._allocated_bytes ) + { allocate_and_copy(img.dimensions(),img._view); } - image& operator=(const image& img) { + // TODO Optimization: use noexcept (requires _view to be nothrow copy constructible) + image(image&& img) : + _view(img._view), + _memory(img._memory), + _align_in_bytes(img._align_in_bytes), + _alloc(std::move(img._alloc)), + _allocated_bytes(img._allocated_bytes) + { + img._view = view_t(); + img._memory = nullptr; + img._align_in_bytes = 0; + img._allocated_bytes = 0; + } + + image& operator=(const image& img) + { if (dimensions() == img.dimensions()) copy_pixels(img._view,_view); - else { + else + { image tmp(img); swap(tmp); } @@ -112,17 +137,83 @@ class image { } template - image& operator=(const Img& img) { + image& operator=(const Img& img) + { if (dimensions() == img.dimensions()) copy_pixels(img._view,_view); - else { + else + { image tmp(img); swap(tmp); } return *this; } - ~image() { + private: + using propagate_allocators = std::true_type; + using no_propagate_allocators = std::false_type; + + template + using choose_pocma = typename std::conditional< + // TODO: Use std::allocator_traits::is_always_equal if available + std::is_empty::value, + std::true_type, + typename std::allocator_traits::propagate_on_container_move_assignment::type + >::type; + + static void exchange_memory(image& lhs, image& rhs) + { + lhs._memory = boost::exchange(rhs._memory, nullptr); + lhs._align_in_bytes = boost::exchange(rhs._align_in_bytes, 0); + lhs._allocated_bytes = boost::exchange(rhs._allocated_bytes, 0); + lhs._view = boost::exchange(rhs._view, image::view_t{}); + }; + + void move_assign(image& img, propagate_allocators) noexcept { + // non-sticky allocator, can adopt the memory, fast + destruct_pixels(_view); + this->deallocate(); + this->_alloc = img._alloc; + exchange_memory(*this, img); + } + + void move_assign(image& img, no_propagate_allocators) { + if (_alloc == img._alloc) { + // allocator stuck to the rhs, but it's equivalent of ours, we can still adopt the memory + destruct_pixels(_view); + this->deallocate(); + exchange_memory(*this, img); + } else { + // cannot propagate the allocator and cannot adopt the memory + if (img._memory) + { + allocate_and_copy(img.dimensions(), img._view); + destruct_pixels(img._view); + img.deallocate(); + img._view = image::view_t{}; + } + else + { + destruct_pixels(this->_view); + this->deallocate(); + this->_view = view_t{}; + } + } + } + + public: + // TODO: Use noexcept(noexcept(move_assign(img, choose_pocma{}))) + // But https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52869 prevents it (fixed in GCC > 9) + image& operator=(image&& img) { + if (this != std::addressof(img)) + // Use rebinded alloc to choose pocma + move_assign(img, choose_pocma{}); + + return *this; + } + + ~image() + { destruct_pixels(_view); deallocate(); } @@ -130,7 +221,8 @@ class image { Alloc& allocator() { return _alloc; } Alloc const& allocator() const { return _alloc; } - void swap(image& img) { // required by MutableContainerConcept + void swap(image& img) // required by MutableContainerConcept + { using std::swap; swap(_align_in_bytes, img._align_in_bytes); swap(_memory, img._memory); @@ -419,12 +511,14 @@ class image { }; template -void swap(image& im1,image& im2) { +void swap(image& im1,image& im2) +{ im1.swap(im2); } template -bool operator==(const image& im1,const image& im2) { +bool operator==(const image& im1,const image& im2) +{ if ((void*)(&im1)==(void*)(&im2)) return true; if (const_view(im1).dimensions()!=const_view(im2).dimensions()) return false; return equal_pixels(const_view(im1),const_view(im2)); @@ -444,7 +538,8 @@ const typename image::view_t& view(image inline -const typename image::const_view_t const_view(const image& img) { +const typename image::const_view_t const_view(const image& img) +{ return static_cast::const_view_t>(img._view); } ///@} diff --git a/test/core/image/image.cpp b/test/core/image/image.cpp index 89f6e15a16..b121834329 100644 --- a/test/core/image/image.cpp +++ b/test/core/image/image.cpp @@ -38,9 +38,54 @@ struct test_constructor_with_dimensions_pixel } }; +struct test_move_constructor +{ + template + void operator()(Image const&) + { + using image_t = Image; + gil::point_t const dimensions{256, 128}; + { + image_t image(fixture::create_image(dimensions.x, dimensions.y, 0)); + + image_t image2(std::move(image)); + BOOST_TEST_EQ(image2.dimensions(), dimensions); + BOOST_TEST_EQ(image.dimensions(), gil::point_t{}); + } + } + static void run() + { + boost::mp11::mp_for_each(test_move_constructor{}); + } +}; + +struct test_move_assignement +{ + template + void operator()(Image const&) + { + using image_t = Image; + gil::point_t const dimensions{256, 128}; + { + image_t image = fixture::create_image(dimensions.x, dimensions.y, 0); + image_t image2 = fixture::create_image(dimensions.x * 2, dimensions.y * 2, 1); + image2 = std::move(image); + BOOST_TEST_EQ(image2.dimensions(), dimensions); + BOOST_TEST_EQ(image.dimensions(), gil::point_t{}); + } + } + static void run() + { + boost::mp11::mp_for_each(test_move_assignement{}); + } +}; + int main() { test_constructor_with_dimensions_pixel::run(); + test_move_constructor::run(); + test_move_assignement::run(); + return ::boost::report_errors(); }