@@ -284,3 +284,152 @@ int test_list()
284
284
debug_log (" Done testing the list." );
285
285
return 0 ;
286
286
}
287
+
288
+ namespace
289
+ {
290
+ /* *
291
+ * \defgroup TypedLinkedObject Non-Standard-Layout Intrusive Lists
292
+ *
293
+ * We can also use C++'s inheritance system for non-standard-layout classes.
294
+ * This requires a bit more work up front, but doesn't rely on `offsetof`.
295
+ *
296
+ * See `test_non_standard_layout_lists()`
297
+ *
298
+ * @{
299
+ */
300
+
301
+ /* *
302
+ * Linkage cell 1 for TypedLinkedObject
303
+ */
304
+ struct BaseCell1 : public ds ::linked_list::cell::PointerF<BaseCell1>
305
+ {
306
+ };
307
+ static_assert (ds::linked_list::cell::HasCellOperationsReset<BaseCell1>);
308
+
309
+ /* *
310
+ * Linkage cell 2 for TypedLinkedObject
311
+ */
312
+ struct BaseCell2 : public ds ::linked_list::cell::PointerF<BaseCell2>
313
+ {
314
+ };
315
+ static_assert (ds::linked_list::cell::HasCellOperationsReset<BaseCell2>);
316
+
317
+ struct DerivedObject : public BaseCell1 , public BaseCell2
318
+ {
319
+ int data;
320
+
321
+ __always_inline struct BaseCell1 *toCell1 ()
322
+ {
323
+ return static_cast <BaseCell1 *>(this );
324
+ }
325
+
326
+ __always_inline static struct DerivedObject *fromCell1 (BaseCell1 *c)
327
+ {
328
+ return static_cast <DerivedObject *>(c);
329
+ }
330
+
331
+ /* *
332
+ * We can even define TypedSentinel "containers" within the derived
333
+ * object itself, which is handy if we want this type to maintain things
334
+ * like "collections of all instances" or "all instances such that ...".
335
+ */
336
+ static inline ds::linked_list::TypedSentinel<DerivedObject,
337
+ BaseCell1,
338
+ &DerivedObject::toCell1,
339
+ DerivedObject::fromCell1>
340
+ sentinel1 = {};
341
+
342
+ __always_inline struct BaseCell2 *toCell2 ()
343
+ {
344
+ return static_cast <BaseCell2 *>(this );
345
+ }
346
+
347
+ __always_inline static struct DerivedObject *fromCell2 (BaseCell2 *c)
348
+ {
349
+ return static_cast <DerivedObject *>(c);
350
+ }
351
+
352
+ DerivedObject ()
353
+ {
354
+ sentinel1.append (this );
355
+ }
356
+
357
+ ~DerivedObject ()
358
+ {
359
+ ds::linked_list::unsafe_remove (toCell1 ());
360
+ }
361
+ };
362
+
363
+ /*
364
+ * This is emphatically not a standard layout class, since we have two base
365
+ * types with members as well as a member in the derived class itself.
366
+ */
367
+ static_assert (!std::is_standard_layout_v<DerivedObject>);
368
+
369
+ /* *
370
+ * @}
371
+ */
372
+
373
+ } // namespace
374
+
375
+ ds::linked_list::TypedSentinel<DerivedObject,
376
+ BaseCell2,
377
+ &DerivedObject::toCell2,
378
+ DerivedObject::fromCell2>
379
+ typedSentinel2 = {};
380
+
381
+ int test_non_standard_layout_list ()
382
+ {
383
+ debug_log (" Testing the non-standard layout list implementation." );
384
+
385
+ // Number of elements we will add to the list in the test.
386
+ static constexpr int NumberOfListElements = 10 ;
387
+
388
+ auto heapAtStart = heap_quota_remaining (MALLOC_CAPABILITY);
389
+
390
+ for (int i = 0 ; i < 10 ; i++)
391
+ {
392
+ auto *d = new DerivedObject ();
393
+ d->data = i;
394
+
395
+ // Thread half the objects onto one list; the constructor threads
396
+ // everything onto the other
397
+ if (i & 1 )
398
+ {
399
+ // new runs constructors, so linkages are initialized and we need
400
+ // not use the emplace variant.
401
+ typedSentinel2.prepend (d);
402
+ }
403
+ }
404
+
405
+ // Free all the objects on the typedSentinel2 ring.
406
+ typedSentinel2.search_safe ([](DerivedObject *d) {
407
+ ds::linked_list::unsafe_remove (d->toCell2 ());
408
+
409
+ // The destructor will manage unlinking us from DerivedObject::sentinel1
410
+ delete d;
411
+
412
+ return false ;
413
+ });
414
+
415
+ DerivedObject::sentinel1.search_safe ([](DerivedObject *d) {
416
+ // Nodes still on the list were never part of the typedSentinel2 ring,
417
+ // so they should have singleton BaseCell2 linkages.
418
+ TEST (ds::linked_list::is_singleton (d->toCell2 ()),
419
+ " Object has bad Cell2 linkages" );
420
+
421
+ delete d;
422
+ return false ;
423
+ });
424
+
425
+ // Check that we didn't leak anything in the process
426
+ auto heapAtEnd = heap_quota_remaining (MALLOC_CAPABILITY);
427
+ TEST (heapAtStart == heapAtEnd,
428
+ " The list leaked {} bytes ({} vs. {})" ,
429
+ heapAtEnd - heapAtStart,
430
+ heapAtStart,
431
+ heapAtEnd);
432
+
433
+ debug_log (" Done testing the non-standard layout list." );
434
+ return 0 ;
435
+ }
0 commit comments