E-MailRelay
gbuffer.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 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 ///
46 /// \code
47 /// Buffer<char> buffer( 100 ) ;
48 /// auto n = fill( buffer ) ;
49 /// buffer.resize( n ) ;
50 /// Foo * foo_p = buffer_cast<Foo*>( buffer ) ;
51 /// ...
52 /// foo_p->~Foo() ;
53 /// \endcode
54 ///
55 /// The implementation of buffer_cast uses placement-new in order to
56 /// avoid the undefined behaviour of a pointer cast. If the buffer_cast
57 /// type has a non-trivial destructor then the destructor should be
58 /// called explicitly through the pointer before the G::Buffer object
59 /// disappears.
60 ///
61 template <typename T>
62 struct Buffer
63 {
64 static_assert( sizeof(T) == 1 , "sizeof t is one" ) ;
65 using value_type = T ;
66 using iterator = T * ;
67 using const_iterator = const T * ;
68 Buffer() noexcept = default ;
69 explicit Buffer( std::size_t n ) : m_n(n) , m_c(n)
70 {
71 m_p = static_cast<T*>( std::malloc(n) ) ; // NOLINT cppcoreguidelines-no-malloc
72 checkalloc() ;
73 }
74 ~Buffer()
75 {
76 std::free( m_p ) ; // NOLINT cppcoreguidelines-no-malloc
77 }
78 void reserve( std::size_t n )
79 {
80 if( n > m_c )
81 {
82 T * new_p = static_cast<T*>( std::realloc(m_p,n) ) ; // NOLINT cppcoreguidelines-no-malloc
83 m_p = checkalloc( new_p ) ;
84 m_c = n ;
85 }
86 }
87 void resize( std::size_t n ) { reserve( n ) ; m_n = n ; }
88 const T & operator[]( std::size_t i ) const noexcept { return *(m_p+i) ; }
89 T & operator[]( std::size_t i ) noexcept { return *(m_p+i) ; }
90 const T & at( std::size_t i ) const { checkindex( i ) ; return *(m_p+i) ; }
91 T & at( std::size_t i ) { checkindex( i ) ; return *(m_p+i) ; }
92 std::size_t size() const noexcept { return m_n ; }
93 std::size_t capacity() const noexcept { return m_c ; }
94 bool empty() const noexcept { return m_n == 0U ; }
95 void clear() noexcept { m_n = 0 ; }
96 void shrink_to_fit() noexcept { if( empty() && m_p ) { std::free( m_p ) ; m_p = nullptr ; m_c = 0U ; } } // NOLINT cppcoreguidelines-no-malloc
97 iterator begin() noexcept { return m_p ? m_p : &m_c0 ; }
98 iterator end() noexcept { return m_p ? (m_p+m_n) : &m_c0 ; }
99 const value_type * data() const noexcept { return m_p ? m_p : &m_c0 ; }
100 value_type * data() noexcept { return m_p ? m_p : &m_c0 ; }
101 const_iterator begin() const noexcept { return m_p ? m_p : &m_c0 ; }
102 const_iterator end() const noexcept { return m_p ? (m_p+m_n) : &m_c0 ; }
103 const_iterator cbegin() const noexcept { return m_p ? m_p : &m_c0 ; }
104 const_iterator cend() const noexcept { return m_p ? (m_p+m_n) : &m_c0 ; }
105 iterator erase( iterator range_begin , iterator range_end ) noexcept
106 {
107 if( range_end == end() )
108 {
109 m_n = std::distance( begin() , range_begin ) ;
110 }
111 else if( range_begin != range_end )
112 {
113 std::size_t range = std::distance( range_begin , range_end ) ;
114 std::size_t end_offset = std::distance( begin() , range_end ) ;
115 std::memmove( range_begin , range_end , m_n-end_offset ) ;
116 m_n -= range ;
117 }
118 return range_begin ;
119 }
120 template <typename U> void insert( iterator p , U range_begin , U range_end )
121 {
122 std::size_t range = std::distance( range_begin , range_end ) ;
123 if( range )
124 {
125 p = makespace( p , range ) ;
126 std::copy( range_begin , range_end , p ) ;
127 }
128 }
129 template <typename U> void insert( iterator p , U * range_begin , U * range_end )
130 {
131 std::size_t range = std::distance( range_begin , range_end ) ;
132 if( range )
133 {
134 p = makespace( p , range ) ;
135 std::memcpy( p , range_begin , range ) ;
136 }
137 }
138 void swap( Buffer<T> & other ) noexcept
139 {
140 std::swap( m_n , other.m_n ) ;
141 std::swap( m_p , other.m_p ) ;
142 std::swap( m_c , other.m_c ) ;
143 }
144 Buffer( const Buffer<T> & other )
145 {
146 if( other.m_n )
147 {
148 resize( other.m_n ) ;
149 std::memcpy( m_p , other.m_p , m_n ) ;
150 }
151 }
152 Buffer( Buffer<T> && other ) noexcept
153 {
154 swap( other ) ;
155 }
156 Buffer<T> & operator=( const Buffer<T> & other )
157 {
158 Buffer<T>(other).swap(*this) ;
159 return *this ;
160 }
161 Buffer<T> & operator=( Buffer<T> && other ) noexcept
162 {
163 Buffer<T>(std::move(other)).swap(*this) ;
164 return *this ;
165 }
166 template <typename U> T * aligned()
167 {
168 if( m_n == 0U || m_p == nullptr ) return nullptr ;
169 void * vp = m_p ;
170 std::size_t space = m_n ;
171 void * result = std::align( alignof(U) , sizeof(U) , vp , space ) ;
172 return static_cast<T*>(result) ;
173 }
174 private:
175 iterator makespace( iterator p , std::size_t range )
176 {
177 G_ASSERT( p >= begin() && p <= end() ) ;
178 G_ASSERT( range != 0U ) ;
179 std::size_t head = std::distance( begin() , p ) ;
180 std::size_t tail = std::distance( p , end() ) ;
181 resize( m_n + range ) ;
182 p = m_p + head ; // since p invalidated by resize()
183 std::memmove( p+range , p , tail ) ;
184 return p ;
185 }
186 void checkalloc() { checkalloc(m_p) ; }
187 T * checkalloc( T * p ) { if( p == nullptr ) throw std::bad_alloc() ; return p ; }
188 void checkindex( std::size_t i ) const { if( i >= m_n ) throw std::out_of_range("G::Buffer") ; }
189 char * m_p {nullptr} ;
190 std::size_t m_n {0U} ;
191 std::size_t m_c {0U} ;
192 value_type m_c0 {'\0'} ;
193 } ;
194
195 template <typename Uptr, typename T = char>
196 Uptr buffer_cast( Buffer<T> & buffer )
197 {
198 using U = typename std::remove_pointer<Uptr>::type ;
199 T * p = buffer.template aligned<U>() ;
200 G_ASSERT( p == nullptr || p == buffer.data() ) ; // assert malloc is behaving
201 if( p != buffer.data() )
202 throw std::bad_cast() ; // buffer too small for a U
203 return new(p) U ;
204 }
205
206 template <typename Uptr, typename T = char>
207 Uptr buffer_cast( Buffer<T> & buffer , std::nothrow_t )
208 {
209 using U = typename std::remove_pointer<Uptr>::type ;
210 T * p = buffer.template aligned<U>() ;
211 G_ASSERT( p == nullptr || p == buffer.data() ) ; // assert malloc is behaving
212 if( p != buffer.data() )
213 return nullptr ; // buffer too small for a U
214 return new(p) U ;
215 }
216
217 template <typename Uptr, typename T = char>
218 Uptr buffer_cast( const Buffer<T> & buffer )
219 {
220 using U = typename std::remove_pointer<Uptr>::type ;
221 return const_cast<Uptr>( buffer_cast<U*>( const_cast<Buffer<T>&>(buffer) ) ) ;
222 }
223
224 template <typename T> void swap( Buffer<T> & a , Buffer<T> & b ) noexcept
225 {
226 a.swap( b ) ;
227 }
228}
229
230#endif
Low-level classes.
Definition: garg.h:36
A substitute for std::vector<char> that has more useful alignment guarantees and explicitly avoids de...
Definition: gbuffer.h:63