]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | [/ |
2 | Copyright 2001, 2003, 2004, 2012 Daryle Walker. | |
3 | ||
4 | Distributed under the Boost Software License, Version 1.0. | |
5 | ||
6 | See accompanying file LICENSE_1_0.txt | |
7 | or copy at http://boost.org/LICENSE_1_0.txt | |
8 | ] | |
9 | ||
10 | [article Base_From_Member | |
11 | [quickbook 1.5] | |
12 | [authors [Walker, Daryle]] | |
13 | [copyright 2001, 2003, 2004, 2012 Daryle Walker] | |
14 | [license | |
15 | Distributed under the Boost Software License, Version 1.0. | |
16 | (See accompanying file LICENSE_1_0.txt or copy at | |
17 | [@http://www.boost.org/LICENSE_1_0.txt]) | |
18 | ] | |
19 | ] | |
20 | ||
21 | [section Rationale] | |
22 | ||
23 | When developing a class, sometimes a base class needs to be initialized | |
24 | with a member of the current class. As a na\u00EFve example: | |
25 | ||
26 | #include <streambuf> /* for std::streambuf */ | |
27 | #include <ostream> /* for std::ostream */ | |
28 | ||
29 | class fdoutbuf | |
30 | : public std::streambuf | |
31 | { | |
32 | public: | |
33 | explicit fdoutbuf( int fd ); | |
34 | //... | |
35 | }; | |
36 | ||
37 | class fdostream | |
38 | : public std::ostream | |
39 | { | |
40 | protected: | |
41 | fdoutbuf buf; | |
42 | public: | |
43 | explicit fdostream( int fd ) | |
44 | : buf( fd ), std::ostream( &buf ) {} | |
45 | //... | |
46 | }; | |
47 | ||
48 | This is undefined because C++'s initialization order mandates that the base | |
49 | class is initialized before the member it uses. [@http://www.moocat.org R. | |
50 | Samuel Klatchko] developed a way around this by using the initialization | |
51 | order in his favor. Base classes are intialized in order of declaration, so | |
52 | moving the desired member to another base class, that is initialized before | |
53 | the desired base class, can ensure proper initialization. | |
54 | ||
55 | A custom base class can be made for this idiom: | |
56 | ||
57 | #include <streambuf> /* for std::streambuf */ | |
58 | #include <ostream> /* for std::ostream */ | |
59 | ||
60 | class fdoutbuf | |
61 | : public std::streambuf | |
62 | { | |
63 | public: | |
64 | explicit fdoutbuf( int fd ); | |
65 | //... | |
66 | }; | |
67 | ||
68 | struct fdostream_pbase | |
69 | { | |
70 | fdoutbuf sbuffer; | |
71 | ||
72 | explicit fdostream_pbase( int fd ) | |
73 | : sbuffer( fd ) {} | |
74 | }; | |
75 | ||
76 | class fdostream | |
77 | : private fdostream_pbase | |
78 | , public std::ostream | |
79 | { | |
80 | typedef fdostream_pbase pbase_type; | |
81 | typedef std::ostream base_type; | |
82 | ||
83 | public: | |
84 | explicit fdostream( int fd ) | |
85 | : pbase_type( fd ), base_type( &sbuffer ) {} | |
86 | //... | |
87 | }; | |
88 | ||
89 | Other projects can use similar custom base classes. The technique is basic | |
90 | enough to make a template, with a sample template class in this library. | |
91 | The main template parameter is the type of the enclosed member. The | |
92 | template class has several (explicit) constructor member templates, which | |
93 | implicitly type the constructor arguments and pass them to the member. The | |
94 | template class uses implicit copy construction and assignment, cancelling | |
95 | them if the enclosed member is non-copyable. | |
96 | ||
97 | Manually coding a base class may be better if the construction and/or | |
98 | copying needs are too complex for the supplied template class, or if the | |
99 | compiler is not advanced enough to use it. | |
100 | ||
101 | Since base classes are unnamed, a class cannot have multiple (direct) base | |
102 | classes of the same type. The supplied template class has an extra template | |
103 | parameter, an integer, that exists solely to provide type differentiation. | |
104 | This parameter has a default value so a single use of a particular member | |
105 | type does not need to concern itself with the integer. | |
106 | ||
107 | [endsect] | |
108 | ||
109 | [section Synopsis] | |
110 | ||
111 | #include <type_traits> /* exposition only */ | |
112 | ||
113 | #ifndef BOOST_BASE_FROM_MEMBER_MAX_ARITY | |
114 | #define BOOST_BASE_FROM_MEMBER_MAX_ARITY 10 | |
115 | #endif | |
116 | ||
117 | template < typename MemberType, int UniqueID = 0 > | |
118 | class boost::base_from_member | |
119 | { | |
120 | protected: | |
121 | MemberType member; | |
122 | ||
123 | #if ``['C++11 is in use]`` | |
124 | template< typename ...T > | |
125 | explicit constexpr base_from_member( T&& ...x ) | |
126 | noexcept( std::is_nothrow_constructible<MemberType, T...>::value ); | |
127 | #else | |
128 | base_from_member(); | |
129 | ||
130 | template< typename T1 > | |
131 | explicit base_from_member( T1 x1 ); | |
132 | ||
133 | template< typename T1, typename T2 > | |
134 | base_from_member( T1 x1, T2 x2 ); | |
135 | ||
136 | //... | |
137 | ||
138 | template< typename T1, typename T2, typename T3, typename T4, | |
139 | typename T5, typename T6, typename T7, typename T8, typename T9, | |
140 | typename T10 > | |
141 | base_from_member( T1 x1, T2 x2, T3 x3, T4 x4, T5 x5, T6 x6, T7 x7, | |
142 | T8 x8, T9 x9, T10 x10 ); | |
143 | #endif | |
144 | }; | |
145 | ||
146 | template < typename MemberType, int UniqueID > | |
147 | class base_from_member<MemberType&, UniqueID> | |
148 | { | |
149 | protected: | |
150 | MemberType& member; | |
151 | ||
152 | explicit constexpr base_from_member( MemberType& x ) | |
153 | noexcept; | |
154 | }; | |
155 | ||
156 | The class template has a first template parameter `MemberType` representing | |
157 | the type of the based-member. It has a last template parameter `UniqueID`, | |
158 | that is an `int`, to differentiate between multiple base classes that use | |
159 | the same based-member type. The last template parameter has a default value | |
160 | of zero if it is omitted. The class template has a protected data member | |
161 | called `member` that the derived class can use for later base classes (or | |
162 | itself). | |
163 | ||
164 | If the appropriate features of C++11 are present, there will be a single | |
165 | constructor template. It implements ['perfect forwarding] to the best | |
166 | constructor call of `member` (if any). The constructor template is marked | |
167 | both `constexpr` and `explicit`. The former will be ignored if the | |
168 | corresponding inner constructor call (of `member`) does not have the marker. | |
169 | The latter binds the other way; always taking effect, even when the inner | |
170 | constructor call does not have the marker. The constructor template | |
171 | propagates the `noexcept` status of the inner constructor call. (The | |
172 | constructor template has a trailing parameter with a default value that | |
173 | disables the template when its signature is too close to the signatures of | |
174 | the automatically-defined non-template copy- and/or move-constructors of | |
175 | `base_from_member`.) | |
176 | ||
177 | On earlier-standard compilers, there is a default constructor and several | |
178 | constructor member templates. These constructor templates can take as many | |
179 | arguments (currently up to ten) as possible and pass them to a constructor | |
180 | of the data member. | |
181 | ||
182 | A specialization for member references offers a single constructor taking | |
183 | a `MemberType&`, which is the only way to initialize a reference. | |
184 | ||
185 | Since C++ does not allow any way to explicitly state the template parameters | |
186 | of a templated constructor, make sure that the arguments are already close | |
187 | as possible to the actual type used in the data member's desired constructor. | |
188 | Explicit conversions may be necessary. | |
189 | ||
190 | The `BOOST_BASE_FROM_MEMBER_MAX_ARITY` macro constant specifies the maximum | |
191 | argument length for the constructor templates. The constant may be overridden | |
192 | if more (or less) argument configurations are needed. The constant may be | |
193 | read for code that is expandable like the class template and needs to | |
194 | maintain the same maximum size. (Example code would be a class that uses | |
195 | this class template as a base class for a member with a flexible set of | |
196 | constructors.) This constant is ignored when C++11 features are present. | |
197 | ||
198 | [endsect] | |
199 | ||
200 | [section Usage] | |
201 | ||
202 | With the starting example, the `fdoutbuf` sub-object needs to be | |
203 | encapsulated in a base class that is inheirited before `std::ostream`. | |
204 | ||
205 | #include <boost/utility/base_from_member.hpp> | |
206 | ||
207 | #include <streambuf> // for std::streambuf | |
208 | #include <ostream> // for std::ostream | |
209 | ||
210 | class fdoutbuf | |
211 | : public std::streambuf | |
212 | { | |
213 | public: | |
214 | explicit fdoutbuf( int fd ); | |
215 | //... | |
216 | }; | |
217 | ||
218 | class fdostream | |
219 | : private boost::base_from_member<fdoutbuf> | |
220 | , public std::ostream | |
221 | { | |
222 | // Helper typedef's | |
223 | typedef boost::base_from_member<fdoutbuf> pbase_type; | |
224 | typedef std::ostream base_type; | |
225 | ||
226 | public: | |
227 | explicit fdostream( int fd ) | |
228 | : pbase_type( fd ), base_type( &member ){} | |
229 | //... | |
230 | }; | |
231 | ||
232 | The base-from-member idiom is an implementation detail, so it should not | |
233 | be visible to the clients (or any derived classes) of `fdostream`. Due to | |
234 | the initialization order, the `fdoutbuf` sub-object will get initialized | |
235 | before the `std::ostream` sub-object does, making the former sub-object | |
236 | safe to use in the latter sub-object's construction. Since the `fdoutbuf` | |
237 | sub-object of the final type is the only sub-object with the name `member` | |
238 | that name can be used unqualified within the final class. | |
239 | ||
240 | [endsect] | |
241 | ||
242 | [section Example] | |
243 | ||
244 | The base-from-member class templates should commonly involve only one | |
245 | base-from-member sub-object, usually for attaching a stream-buffer to an | |
246 | I/O stream. The next example demonstrates how to use multiple | |
247 | base-from-member sub-objects and the resulting qualification issues. | |
248 | ||
249 | #include <boost/utility/base_from_member.hpp> | |
250 | ||
251 | #include <cstddef> /* for NULL */ | |
252 | ||
253 | struct an_int | |
254 | { | |
255 | int y; | |
256 | ||
257 | an_int( float yf ); | |
258 | }; | |
259 | ||
260 | class switcher | |
261 | { | |
262 | public: | |
263 | switcher(); | |
264 | switcher( double, int * ); | |
265 | //... | |
266 | }; | |
267 | ||
268 | class flow_regulator | |
269 | { | |
270 | public: | |
271 | flow_regulator( switcher &, switcher & ); | |
272 | //... | |
273 | }; | |
274 | ||
275 | template < unsigned Size > | |
276 | class fan | |
277 | { | |
278 | public: | |
279 | explicit fan( switcher ); | |
280 | //... | |
281 | }; | |
282 | ||
283 | class system | |
284 | : private boost::base_from_member<an_int> | |
285 | , private boost::base_from_member<switcher> | |
286 | , private boost::base_from_member<switcher, 1> | |
287 | , private boost::base_from_member<switcher, 2> | |
288 | , protected flow_regulator | |
289 | , public fan<6> | |
290 | { | |
291 | // Helper typedef's | |
292 | typedef boost::base_from_member<an_int> pbase0_type; | |
293 | typedef boost::base_from_member<switcher> pbase1_type; | |
294 | typedef boost::base_from_member<switcher, 1> pbase2_type; | |
295 | typedef boost::base_from_member<switcher, 2> pbase3_type; | |
296 | ||
297 | typedef flow_regulator base1_type; | |
298 | typedef fan<6> base2_type; | |
299 | ||
300 | public: | |
301 | system( double x ); | |
302 | //... | |
303 | }; | |
304 | ||
305 | system::system( double x ) | |
306 | : pbase0_type( 0.2 ) | |
307 | , pbase1_type() | |
308 | , pbase2_type( -16, &this->pbase0_type::member.y ) | |
309 | , pbase3_type( x, static_cast<int *>(NULL) ) | |
310 | , base1_type( pbase3_type::member, pbase1_type::member ) | |
311 | , base2_type( pbase2_type::member ) | |
312 | { | |
313 | //... | |
314 | } | |
315 | ||
316 | The final class has multiple sub-objects with the name `member`, so any | |
317 | use of that name needs qualification by a name of the appropriate base | |
318 | type. (Using `typedef`s ease mentioning the base types.) However, the fix | |
319 | introduces a new problem when a pointer is needed. Using the address | |
320 | operator with a sub-object qualified with its class's name results in a | |
321 | pointer-to-member (here, having a type of `an_int boost::base_from_member< | |
322 | an_int, 0> :: *`) instead of a pointer to the member (having a type of | |
323 | `an_int *`). The new problem is fixed by qualifying the sub-object with | |
324 | `this->` and is needed just for pointers, and not for references or values. | |
325 | ||
326 | There are some argument conversions in the initialization. The constructor | |
327 | argument for `pbase0_type` is converted from `double` to `float`. The first | |
328 | constructor argument for `pbase2_type` is converted from `int` to `double`. | |
329 | The second constructor argument for `pbase3_type` is a special case of | |
330 | necessary conversion; all forms of the null-pointer literal in C++ (except | |
331 | `nullptr` from C++11) also look like compile-time integral expressions, so | |
332 | C++ always interprets such code as an integer when it has overloads that can | |
333 | take either an integer or a pointer. The last conversion is necessary for the | |
334 | compiler to call a constructor form with the exact pointer type used in | |
335 | `switcher`'s constructor. (If C++11's `nullptr` is used, it still needs a | |
336 | conversion if multiple pointer types can be accepted in a constructor call | |
337 | but `std::nullptr_t` cannot.) | |
338 | ||
339 | [endsect] | |
340 | ||
341 | [section Acknowledgments] | |
342 | ||
343 | * [@http://www.boost.org/people/ed_brey.htm Ed Brey] suggested some interface | |
344 | changes. | |
345 | ||
346 | * [@http://www.moocat.org R. Samuel Klatchko] ([@mailto:rsk@moocat.org | |
347 | rsk@moocat.org], [@mailto:rsk@brightmail.com rsk@brightmail.com]) invented | |
348 | the idiom of how to use a class member for initializing a base class. | |
349 | ||
350 | * [@http://www.boost.org/people/dietmar_kuehl.htm Dietmar Kuehl] popularized the | |
351 | base-from-member idiom in his [@http://www.informatik.uni-konstanz.de/~kuehl/c++/iostream/ | |
352 | IOStream example classes]. | |
353 | ||
354 | * Jonathan Turkanis supplied an implementation of generating the constructor | |
355 | templates that can be controlled and automated with macros. The | |
356 | implementation uses the [@../../../preprocessor/index.html Preprocessor library]. | |
357 | ||
358 | * [@http://www.boost.org/people/daryle_walker.html">Daryle Walker] started the | |
359 | library. Contributed the test file [@../../base_from_member_test.cpp | |
360 | base_from_member_test.cpp]. | |
361 | ||
362 | [endsect] | |
363 |