E-MailRelay
gbuffer.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gbuffer.h
19///
20
21#ifndef G_BUFFER_H
22#define G_BUFFER_H
23
24#include "gdef.h"
25#include "gassert.h"
26#include <stdexcept>
27#include <utility>
28#include <algorithm>
29#include <type_traits>
30#include <cstring>
31#include <memory>
32#include <new>
33
34namespace G
35{
36 //| \class G::Buffer
37 /// A substitute for std::vector<char> that has more useful alignment
38 /// guarantees and explicitly avoids default initialisation of each
39 /// element. The alignment is that of std::malloc(), ie. std::max_align_t.
40 /// (See also posix_memalign().)
41 ///
42 /// The buffer_cast() free function can be used to return a pointer
43 /// to the start of the buffer for some aggregate type, throwing if
44 /// the buffer is too small for a complete object:
45 /// \code
46 /// Buffer<char> buffer( 100 ) ;
47 /// auto n = fill( buffer ) ;
48 /// buffer.resize( n ) ;
49 /// Foo * foo_p = buffer_cast<Foo*>( buffer ) ;
50 /// foo_p->~Foo() ;
51 /// \endcode
52 ///
53 /// The implementation of buffer_cast uses placement-new in order to
54 /// avoid the undefined behaviour of a pointer cast. In some cases this
55 /// means that the destructor should be called explicitly through the
56 /// pointer.
57 ///
58 template <typename T>
59 struct Buffer
60 {
61 static_assert( sizeof(T) == 1 , "sizeof t is one" ) ;
62 using value_type = T ;
63 using iterator = T * ;
64 using const_iterator = const T * ;
65 Buffer() noexcept = default ;
66 explicit Buffer( std::size_t n ) : m_n(n) , m_c(n)
67 {
68 m_p = static_cast<T*>(std::malloc(n)) ; // NOLINT cppcoreguidelines-no-malloc
69 checkalloc() ;
70 }
71 ~Buffer()
72 {
73 std::free( m_p ) ; // NOLINT cppcoreguidelines-no-malloc
74 }
75 void reserve( std::size_t n )
76 {
77 if( n > m_c )
78 {
79 T * new_p = static_cast<T*>( std::realloc(m_p,n) ) ; // NOLINT cppcoreguidelines-no-malloc
80 m_p = checkalloc( new_p ) ;
81 m_c = n ;
82 }
83 }
84 void resize( std::size_t n ) { reserve( n ) ; m_n = n ; }
85 const T & operator[]( std::size_t i ) const noexcept { return *(m_p+i) ; }
86 T & operator[]( std::size_t i ) noexcept { return *(m_p+i) ; }
87 const T & at( std::size_t i ) const { checkindex( i ) ; return *(m_p+i) ; }
88 T & at( std::size_t i ) { checkindex( i ) ; return *(m_p+i) ; }
89 std::size_t size() const noexcept { return m_n ; }
90 std::size_t capacity() const noexcept { return m_c ; }
91 bool empty() const noexcept { return m_n == 0U ; }
92 void clear() noexcept { m_n = 0 ; }
93 void shrink_to_fit() noexcept { if( empty() && m_p ) { std::free(m_p) ; m_p = nullptr ; m_c = 0U ; } } // NOLINT cppcoreguidelines-no-malloc
94 iterator begin() noexcept { return m_p ? m_p : &m_c0 ; }
95 iterator end() noexcept { return m_p ? (m_p+m_n) : &m_c0 ; }
96 const value_type * data() const noexcept { return m_p ? m_p : &m_c0 ; }
97 value_type * data() noexcept { return m_p ? m_p : &m_c0 ; }
98 const_iterator begin() const noexcept { return m_p ? m_p : &m_c0 ; }
99 const_iterator end() const noexcept { return m_p ? (m_p+m_n) : &m_c0 ; }
100 const_iterator cbegin() const noexcept { return m_p ? m_p : &m_c0 ; }
101 const_iterator cend() const noexcept { return m_p ? (m_p+m_n) : &m_c0 ; }
102 iterator erase( iterator range_begin , iterator range_end ) noexcept
103 {
104 if( range_end == end() )
105 {
106 m_n = std::distance( begin() , range_begin ) ;
107 }
108 else if( range_begin != range_end )
109 {
110 std::size_t range = std::distance( range_begin , range_end ) ;
111 std::size_t end_offset = std::distance( begin() , range_end ) ;
112 std::memmove( range_begin , range_end , m_n-end_offset ) ;
113 m_n -= range ;
114 }
115 return range_begin ;
116 }
117 template <typename U> void insert( iterator p , U range_begin , U range_end )
118 {
119 std::size_t range = std::distance( range_begin , range_end ) ;
120 if( range )
121 {
122 p = makespace( p , range ) ;
123 std::copy( range_begin , range_end , p ) ;
124 }
125 }
126 template <typename U> void insert( iterator p , U * range_begin , U * range_end )
127 {
128 std::size_t range = std::distance( range_begin , range_end ) ;
129 if( range )
130 {
131 p = makespace( p , range ) ;
132 std::memcpy( p , range_begin , range ) ;
133 }
134 }
135 void swap( Buffer<T> & other ) noexcept
136 {
137 std::swap( m_n , other.m_n ) ;
138 std::swap( m_p , other.m_p ) ;
139 std::swap( m_c , other.m_c ) ;
140 }
141 Buffer( const Buffer<T> & other )
142 {
143 if( other.m_n )
144 {
145 resize( other.m_n ) ;
146 std::memcpy( m_p , other.m_p , m_n ) ;
147 }
148 }
149 Buffer( Buffer<T> && other ) noexcept
150 {
151 swap( other ) ;
152 }
153 Buffer<T> & operator=( const Buffer<T> & other )
154 {
155 Buffer<T>(other).swap(*this) ;
156 return *this ;
157 }
158 Buffer<T> & operator=( Buffer<T> && other ) noexcept
159 {
160 Buffer<T>(std::move(other)).swap(*this) ;
161 return *this ;
162 }
163 template <typename U> T * aligned()
164 {
165 if( m_n == 0U || m_p == nullptr ) return nullptr ;
166 void * vp = m_p ;
167 std::size_t space = m_n ;
168 void * result = std::align( alignof(U) , sizeof(U) , vp , space ) ;
169 return static_cast<T*>(result) ;
170 }
171 private:
172 iterator makespace( iterator p , std::size_t range )
173 {
174 G_ASSERT( p >= begin() && p <= end() ) ;
175 G_ASSERT( range != 0U ) ;
176 std::size_t head = std::distance( begin() , p ) ;
177 std::size_t tail = std::distance( p , end() ) ;
178 resize( m_n + range ) ;
179 p = m_p + head ; // since p invalidated by resize()
180 std::memmove( p+range , p , tail ) ;
181 return p ;
182 }
183 void checkalloc() { checkalloc(m_p) ; }
184 T * checkalloc( T * p ) { if( p == nullptr ) throw std::bad_alloc() ; return p ; }
185 void checkindex( std::size_t i ) const { if( i >= m_n ) throw std::out_of_range("G::Buffer") ; }
186 char * m_p{nullptr} ;
187 std::size_t m_n{0U} ;
188 std::size_t m_c{0U} ;
189 value_type m_c0{'\0'} ;
190 } ;
191
192 template <typename Uptr, typename T = char>
193 Uptr buffer_cast( Buffer<T> & buffer )
194 {
195 using U = typename std::remove_pointer<Uptr>::type ;
196 T * p = buffer.template aligned<U>() ;
197 G_ASSERT( p == nullptr || p == &buffer[0] ) ; // assert malloc is behaving
198 if( p != &buffer[0] )
199 throw std::bad_cast() ; // buffer too small for a U
200 return new(p) U ;
201 }
202
203 template <typename Uptr, typename T = char>
204 Uptr buffer_cast( Buffer<T> & buffer , std::nothrow_t )
205 {
206 using U = typename std::remove_pointer<Uptr>::type ;
207 T * p = buffer.template aligned<U>() ;
208 G_ASSERT( p == nullptr || p == &buffer[0] ) ; // assert malloc is behaving
209 if( p != &buffer[0] )
210 return nullptr ; // buffer too small for a U
211 return new(p) U ;
212 }
213
214 template <typename Uptr, typename T = char>
215 Uptr buffer_cast( const Buffer<T> & buffer )
216 {
217 using U = typename std::remove_pointer<Uptr>::type ;
218 return const_cast<Uptr>( buffer_cast<U*>( const_cast<Buffer<T>&>(buffer) ) ) ;
219 }
220
221 template <typename T> void swap( Buffer<T> & a , Buffer<T> & b ) noexcept
222 {
223 a.swap( b ) ;
224 }
225}
226
227#endif
Low-level classes.
Definition: galign.h:28
void * align(const char *buffer, std::size_t buffer_size)
Returns a pointer inside the given buffer that is aligned for values of type T.
Definition: galign.h:70
A substitute for std::vector<char> that has more useful alignment guarantees and explicitly avoids de...
Definition: gbuffer.h:60