forked from cheng/wallet
4e671723fe
Also extensively modified the process of generating html files from markdown files
610 lines
22 KiB
Markdown
610 lines
22 KiB
Markdown
---
|
||
title:
|
||
C++ Automatic Memory Management
|
||
---
|
||
# Memory Safety
|
||
Modern, mostly memory safe C++, is enforced by:\
|
||
|
||
- Microsoft safety checker
|
||
- Guidelines
|
||
- language checker
|
||
|
||
`$ clang-tidy test.cpp -checks=clang-analyzer-cplusplus*, cppcoreguidelines-*, modernize-*` will catch most of the issues that esr
|
||
complains about, in practice usually all of them, though I suppose that as
|
||
the project gets bigger, some will slip through.
|
||
|
||
static_assert(__cplusplus >= 201703, "C version of out of date");
|
||
|
||
Adds the std::span type, which makes pointer handling a whole lot simpler and safer.
|
||
The size of the array pointed to is kept with the pointer for safe iteration and bounds checking during pointer maths. Also, translates std::array and old type C arrays to the same type, which makes life much simpler and safer. You get all the new good range stuff from both of them.
|
||
|
||
Modern C++ as handles arrays as arrays where possible, but they quickly
|
||
decay to pointers – which you avoid using spans. std::array is a C array
|
||
whose size is known at compile time, and which is protected from decay to
|
||
a pointer. std::vector is a dynamically resizable and insertable array
|
||
protected from decay to a pointer – which can have significant overheads.
|
||
std::make_unique, std::make_shared create pointers to memory managed
|
||
objects. (But single objects, not an array, use spans for pointer
|
||
arithmetic)
|
||
|
||
auto sp = std::make_shared<int>(42);
|
||
std::weak_ptr<T> wp{sp};
|
||
|
||
# Array sizing and allocation
|
||
|
||
/* This code creates a bunch of "brown dog" strings on the heap to test automatic memory management. */
|
||
char ca[]{ "red dog" }; //Automatic array sizing
|
||
std::array<char,8> arr{"red dog"}; //Requires #include <array>
|
||
/* No automatic array sizing, going to have to count your initializer list. */
|
||
/* The pointer of the underlying array is referenced by &arr[0] but arr is not the underlying array, nor a pointer to it. */
|
||
/* [0] invokes operator[], and operator[] is the member function that accesses the underlying array.*/
|
||
/* The size of the underlying array is referenced by arr.size();*/
|
||
/* size known at compile time, array can be returned from a function getting the benefits of stack allocation.*/
|
||
// can be passed around like POD
|
||
char *p = new char[10]{ "brown dog" }; //No automatic array
|
||
// sizing for new
|
||
std::unique_ptr<char[]>puc{ p }; // Now you do not have
|
||
// to remember to delete p
|
||
auto puc2 = std::move(puc); /* No copy constructor. Pass by reference, or pass a view, such as a span.*/
|
||
std::unique_ptr<char> puc3{ new char[10]{ "brown dog" } };
|
||
/* Array size unknown at compile or run time, needs a span, and you have to manually count the initialization list. */
|
||
/* Compiler guards against overflow, but does not default to the correct size.*/
|
||
/* You can just guess a way too small size, and the compiler in its error message will tell you what the size should be. */
|
||
auto pu = std::make_unique<char[]>(10); // uninitialized,
|
||
// needs procedural initialization.
|
||
|
||
/* span can be trivially created from a compile time declared array, an std:array or from a run time std:: vector, but then these things already have the characteristics of a span, and they own their own storage. */
|
||
/* You would use a span to point into an array, for example a large blob containing smaller blobs.*/
|
||
|
||
// Placement New:
|
||
char *buf = new char[1000]; //pre-allocated buffer
|
||
char *p = buf;
|
||
MyObject *pMyObject = new (p) MyObject();
|
||
p += (sizeof(MyObject+7)/8)*8
|
||
/* Problem is that you will have to explictly call the destructor on each object before freeing your buffer. */
|
||
/* If your objects are POD plus code for operating on POD, you don’t have to worry about destructors.*/
|
||
// A POD object cannot do run time polymorphism.
|
||
/* The pointer referencing it has to be of the correct compile time type, and it has to explicitly have the default constructor when constructed with no arguments.*/
|
||
/* If, however, you are building a tree in the pre-allocated buffer, no sweat. */
|
||
/* You just destruct the root of the tree, and it recursively destructs all its children. */
|
||
/* If you want an arbitrary graph, just make sure you have owning and non owning pointers, and the owning pointers form a tree. */
|
||
/* Anything you can do with run time polymorphism, you can likely do with a type flag.*/
|
||
|
||
static_assert ( std::is_pod<MyType>() , "MyType for some reason is not POD" );
|
||
class MyClass
|
||
{
|
||
public:
|
||
MyClass()=default; // Otherwise unlikely to be POD
|
||
MyClass& operator=(const MyClass&) = default; // default assignment Not actually needed, but just a reminder.
|
||
};
|
||
|
||
### alignment
|
||
|
||
```c++
|
||
// every object of type struct_float will be aligned to alignof(float) boundary
|
||
// (usually 4)
|
||
struct alignas(float) struct_float {
|
||
// your definition here
|
||
};
|
||
|
||
// every object of type sse_t will be aligned to 256-byte boundary
|
||
struct alignas(256) sse_t
|
||
{
|
||
float sse_data[4];
|
||
};
|
||
|
||
// the array "cacheline" will be aligned to 128-byte boundary
|
||
alignas(128) char cacheline[128];
|
||
```
|
||
|
||
# Construction, assignment, and destruction
|
||
|
||
six things: ([default
|
||
constructor](https://en.cppreference.com/w/cpp/language/default_constructor),
|
||
[copy
|
||
constructor](https://en.cppreference.com/w/cpp/language/copy_constructor),
|
||
[move
|
||
constructor](https://en.cppreference.com/w/cpp/language/move_constructor),
|
||
[copy
|
||
assignment](https://en.cppreference.com/w/cpp/language/copy_assignment),
|
||
[move
|
||
assignment](https://en.cppreference.com/w/cpp/language/move_assignment)
|
||
and [destructor](https://en.cppreference.com/w/cpp/language/destructor))
|
||
are generated by default – except when they are not.
|
||
|
||
So it is arguably a good idea to explicitly declare them as default or
|
||
deleted.
|
||
|
||
Copy constructors
|
||
|
||
A(const A& a)
|
||
|
||
Copy assignment
|
||
|
||
A& operator=(const A other)
|
||
|
||
Move constructors
|
||
|
||
class_name ( class_name && other)
|
||
A(A&& o)
|
||
D(D&&) = default;
|
||
|
||
Move assignment operator
|
||
|
||
V& operator=(V&& other)
|
||
|
||
Move constructors
|
||
|
||
class_name ( class_name && )
|
||
|
||
## rvalue references
|
||
|
||
Move constructors and copy constructors primarily exist to tell the
|
||
compiler how to handle temporary values, rvalues, that have references to possibly
|
||
costly resources.
|
||
|
||
`class_name&&` is rvalue reference, the canonical example being a reference to a compiler generated temporary.
|
||
|
||
The primary purpose of rvalue references is to support move semantics in
|
||
objects that reference resources, primarily unique_pointer.
|
||
|
||
`std::move(t)` is equivalent to `static_cast<decltype(t)&&>(t)`, causing move
|
||
semantics to be generated by the compiler.
|
||
|
||
`t`, the compiler assumes is converted by your move constructor or move assignment into a valid state where your destructor will not need to anything very costly.
|
||
|
||
`std::forward(t)` causes move semantics to be invoked iff the thing referenced
|
||
is an rvalue, typically a compiler generated temporary, *conditionally*
|
||
forwarding the resources.
|
||
|
||
where `std::forward` is defined as follows:
|
||
|
||
template< class T > struct remove_reference {
|
||
typedef T type;
|
||
};
|
||
template< class T > struct remove_reference<T&> {
|
||
typedef T type;
|
||
};
|
||
template< class T > struct remove_reference<T&&> {
|
||
typedef T type;
|
||
};
|
||
|
||
template<class S>
|
||
S&& forward(typename std::remove_reference<S>::type& a) noexcept
|
||
{
|
||
return static_cast<S&&>(a);
|
||
}
|
||
|
||
`std::move(t)` and `std::forward(t)` don't actually perform any action
|
||
in themselves, rather they cause the code referencing `t` to use the intended
|
||
copy and intended assignment.
|
||
|
||
## constructors and destructors
|
||
|
||
If you declare the destructor deleted that prevents the compiler from
|
||
generating its own, possibly disastrous, destructor, but then, of
|
||
course, you have to define your own destructor with the exact same
|
||
signature, which would ordinarily stop the compiler from doing that
|
||
anyway.
|
||
|
||
When you declare your own constructors, copiers, movers, and deleters,
|
||
you should generally mark them noexcept.
|
||
|
||
struct foo {
|
||
foo() noexcept {}
|
||
foo( const foo & ) noexcept { }
|
||
foo( foo && ) noexcept { }
|
||
~foo() {}
|
||
};
|
||
|
||
Destructors are noexcept by default. If a destructor throws an exception as
|
||
a result of a destruction caused by an exception, the result is undefined,
|
||
and usually very bad. This problem is resolved in complicated ad hoc
|
||
ways that are unlikely to be satisfactory.
|
||
|
||
If you need to define a copy constructor, probably also need to define
|
||
an assignment operator.
|
||
|
||
t2 = t1; /* calls assignment operator, same as "t2.operator=(t1);" */
|
||
Test t3 = t1; /* calls copy constructor, same as "Test t3(t1);" */
|
||
|
||
## casts
|
||
|
||
You probably also want casts. The surprise thing about a cast operator
|
||
is that its return type is not declared, nor permitted to be declared,
|
||
DRY. Operator casts are the same thing as constructors, except declared
|
||
in the source class instead of the destination class, hence most useful
|
||
when you are converting to a generic C type, or to the type of an
|
||
external library that you do not want to change.
|
||
|
||
struct X {
|
||
int y;
|
||
operator int(){ return y; }
|
||
operator const int&(){ return y; } /* C habits would lead you to incorrectly expect "return &y;", which is what is implied under the hood. */
|
||
operator int*(){ return &y; } // Hood is opened.
|
||
};
|
||
|
||
Mpir, the Visual Studio skew of GMP infinite precision library, has some
|
||
useful and ingenious template code for converting C type functions of
|
||
the form `SetAtoBplusC(void * a, void * b, void * c);` into C++
|
||
expressions of the form `a = b+c*d;`. It has a bunch of intermediate
|
||
types with no real existence, `__gmp_expr<>` and `__gmp_binary_expr<>`
|
||
and methods with no real existence, which generate the appropriate
|
||
calls, a templated function of potentially unlimited complexity, to
|
||
convert such an expression into the relevant C type calls using
|
||
pointers. See section mpir-3.0.0.pdf, section 17.5 “C++ Internals”.
|
||
|
||
I don’t understand the Mpir code, but I think what is happening is that
|
||
at run time, the binary expression operating on two base types creates a
|
||
transient object on the stack containing pointers to the two base types,
|
||
and the assignment operator and copy create operator then call the
|
||
appropriate C code, and the operator for entities of indefinite
|
||
complexity creates base type values on the stack and a binary expression
|
||
operator pointing to them.
|
||
|
||
Simpler, but introducing a redundant copy, to always generate
|
||
intermediate values on the stack, since we have fixed length objects
|
||
that do not need dynamic heap memory allocation, not that costly, and
|
||
they are not that big, at worst thirty two bytes, so clever code is apt
|
||
to cost in overheads of pointer management
|
||
|
||
That just means we are putting 256 bits of intermediate data on the
|
||
stack instead of 128, hardly a cost worth worrying about. And in the
|
||
common bad case, (a+b)\*(c+d) clever coding would only save one stack
|
||
allocation and redundant copy.
|
||
|
||
# Template specialization
|
||
|
||
namespace N {
|
||
template<class T> class Y { /*...*/ }; // primary template
|
||
template<> class Y<double> ; // forward declare specialization for double
|
||
}
|
||
template<>
|
||
class N::Y<double> { /*...*/ }; // OK: specialization in same namespace
|
||
|
||
is used when you have sophisticated template code, because you have to
|
||
use recursion for looping as the Mpir system uses it to evaluate an
|
||
arbitrarily complex recursive expression – but I think my rather crude
|
||
implementation will not be nearly so clever.
|
||
|
||
extern template int fun(int);
|
||
/*prevents redundant instantiation of fun in this compilation unit – and thus renders the code for fun unnecessary in this compilation unit.*/
|
||
|
||
# Template traits, introspection
|
||
|
||
Template traits: C++ has no syntactic sugar to ensure that your template
|
||
is only called using the classes you intend it to be called with.
|
||
|
||
Often you want different templates for classes that implement similar functionality in different ways.
|
||
|
||
This is the entire very large topic of template time, compile time code, which is a whole new ball of wax that needs to be dealt with elsewhere
|
||
|
||
# Abstract and virtual
|
||
|
||
An abstract base class is a base class that contains a pure virtual
|
||
function ` virtual void features() = 0;`.
|
||
|
||
A class can have a virtual destructor, but not a virtual constructor.
|
||
|
||
If a class contains virtual functions, then the default constructor has
|
||
to initialize the pointer to the vtable. Otherwise, the default
|
||
constructor for a POD class is empty, which implies that the default
|
||
destructor is empty.
|
||
|
||
The copy and swap copy assignment operator, a rather slow and elaborate
|
||
method of guaranteeing that an exception will leave the system in a good
|
||
state, is never generated by default, since it always relates to rather
|
||
clever RAII.
|
||
|
||
An interface class is a class that has no member variables, and where
|
||
all of the functions are pure virtual! In other words, the class is
|
||
purely a definition, and has no actual implementation. Interfaces are
|
||
useful when you want to define the functionality that derived classes
|
||
must implement, but leave the details of how the derived class
|
||
implements that functionality entirely up to the derived class.
|
||
|
||
Interface classes are often named beginning with an I. Here’s a sample
|
||
interface class:.
|
||
|
||
class IErrorLog
|
||
{
|
||
public:
|
||
virtual bool openLog(const char *filename) = 0;
|
||
virtual bool closeLog() = 0;
|
||
|
||
virtual bool writeError(const char *errorMessage) = 0;
|
||
|
||
virtual ~IErrorLog() {} // make a virtual destructor in case we delete an IErrorLog pointer, so the proper derived destructor is called
|
||
// Notice that the virtual destructor is declared to be trivial, but not declared =0;
|
||
};
|
||
|
||
[Override
|
||
specifier](https://en.cppreference.com/w/cpp/language/override)
|
||
|
||
struct A
|
||
{
|
||
virtual void foo();
|
||
void bar();
|
||
};
|
||
|
||
struct B : A
|
||
{
|
||
void foo() const override; // Error: B::foo does not override A::foo
|
||
// (signature mismatch)
|
||
void foo() override; // OK: B::foo overrides A::foo
|
||
void bar() override; // Error: A::bar is not virtual
|
||
};
|
||
|
||
Similarly [Final
|
||
specifier](https://en.cppreference.com/w/cpp/language/final)
|
||
|
||
[To obtain aligned
|
||
storage](http://www.cplusplus.com/reference/type_traits/aligned_storage/)for
|
||
use with placement new
|
||
|
||
void* p = aligned_alloc(sizeof(NotMyClass));
|
||
MyClass* pmc = new (p) MyClass; //Placement new.
|
||
// ...
|
||
pmc->~MyClass(); //Explicit call to destructor.
|
||
aligned_free(p);.
|
||
|
||
# GSL: Guideline Support Library
|
||
|
||
The Guideline Support Library (GSL) contains functions and types that
|
||
are suggested for use by the C++ Core Guidelines maintained by the
|
||
Standard C++ Foundation. This repo contains [Microsoft’s implementation
|
||
of GSL](https://github.com/Microsoft/GSL).
|
||
|
||
git clone https://github.com/Microsoft/GSL.git
|
||
cd gsl
|
||
git tag
|
||
git checkout tags/v2.0.0
|
||
|
||
Which implementation mostly works on gcc/Linux, but is canonical on
|
||
Visual Studio.
|
||
|
||
For usage of spans ([the replacement for bare naked non owning pointers
|
||
subject to pointer
|
||
arithmetic)](http://codexpert.ro/blog/2016/03/07/guidelines-support-library-review-spant/)
|
||
|
||
For usage of string spans ([String
|
||
spans](http://codexpert.ro/blog/2016/03/21/guidelines-support-library-review-string_span/)
|
||
These are pointers to char arrays. There does not seem to be a UTF‑8
|
||
string_span.
|
||
|
||
GSL is a preview of C++20, as boost contained a preview of C++11.
|
||
|
||
It is disturbingly lacking in official documentation, perhaps because
|
||
still subject to change.
|
||
|
||
[Unofficial
|
||
documentation](http://modernescpp.com/index.php/c-core-guideline-the-guidelines-support-library)
|
||
|
||
It provides an optional fix for C’s memory management problems, while
|
||
still retaining backward compatibility to the existing pile of rusty
|
||
razor blades and broken glass.
|
||
|
||
# The Curiously Recurring Template Pattern
|
||
|
||
[CRTP](https://www.fluentcpp.com/2017/05/16/what-the-crtp-brings-to-code/),
|
||
makes the relationship between the templated base class or classes and
|
||
the derived class cyclic, so that the derived class tends to function as
|
||
real base class. Useful for mixin classes.
|
||
|
||
template <typename T> class Mixin1{
|
||
public:
|
||
// ...
|
||
void doSomething() //using the other mixin classes and the derived class T
|
||
{
|
||
T& derived = static_cast<T&>(*this);
|
||
// use derived...
|
||
}
|
||
private:
|
||
mixin1(){}; // prevents the class from being used outside the mix)
|
||
friend T;
|
||
};
|
||
|
||
template <typename T> class Mixin2{
|
||
{
|
||
public:
|
||
// ...
|
||
void doSomethingElse()
|
||
{
|
||
T& derived = static_cast<T&>(*this);
|
||
// use derived...
|
||
}
|
||
private:
|
||
Mixin2(){};
|
||
friend T;
|
||
};
|
||
|
||
class composite: public mixin1<composite>, public mixin2<composite>{
|
||
composite( int x, char * y): mixin1(x), mixin2(y[0]) { ...}
|
||
composite():composite(7,"a" ){ ...}
|
||
}
|
||
|
||
# Aggregate initialization
|
||
|
||
A class of aggregate type has no constructors – the aggregate
|
||
constructor is implied default.
|
||
|
||
A class can be explicitly defined to take aggregate initialization
|
||
|
||
Class T{
|
||
T(std::initializer_list<const unsigned char> in){
|
||
for (auto i{in.begin); i<in.end(); i++){
|
||
do stuff with i
|
||
}
|
||
}
|
||
|
||
but that does not make it of aggregate type. Aggregate type has *no*
|
||
constructors except default and deleted constructors
|
||
|
||
# functional programming
|
||
|
||
To construct a lambda in the heap:
|
||
|
||
auto p = new auto([a,b,c](){})
|
||
|
||
Objects inside the lambda are constructed in the heap.
|
||
|
||
similarly placement `new`, and `unique_ptr`.
|
||
|
||
To template a function that takes a lambda argument:
|
||
|
||
template <typename F>
|
||
void myFunction(F&& lambda){
|
||
//some things
|
||
|
||
You can put a lambda in a class using decltype,and pass it around for
|
||
continuations, though you would probably need to template the class:
|
||
|
||
template<class T>class foo {
|
||
public:
|
||
T func;
|
||
foo(T in) :func{ in } {}
|
||
auto test(int x) { return func(x); }
|
||
};
|
||
....
|
||
auto bar = [](int x)->int {return x + 1; };
|
||
foo<(bar)>foobar(bar);
|
||
|
||
But we had to introduce a name, bar, so that decltype would have
|
||
something to work with, which lambdas are intended to avoid. If we are
|
||
going to have to introduce a compile time name, easier to do it as an
|
||
old fashioned function, method, or functor, as a method of a class that
|
||
is very possibly pod.
|
||
|
||
If we are sticking a lambda around to be called later, might copy it by
|
||
value into a templated class, or might put it on the heap.
|
||
|
||
auto bar = []() {return 5;};
|
||
|
||
You can give it to a std::function:
|
||
|
||
auto func_bar = std::function<int()>(bar);
|
||
|
||
In this case, it will get a copy of the value of bar. If bar had
|
||
captured anything by value, there would be two copies of those values on
|
||
the stack; one in bar, and one in func_bar.
|
||
|
||
When the current scope ends, func_bar will be destroyed, followed by
|
||
bar, as per the rules of cleaning up stack variables.
|
||
|
||
You could just as easily allocate one on the heap:
|
||
|
||
auto bar_ptr = std::make_unique(bar);
|
||
|
||
std::function <int(int)> increm{[](int arg{return arg+1;}}
|
||
|
||
presumably does this behind the scenes
|
||
|
||
On reflection we could probably use this method to produce a
|
||
templated function that stored a lambda somewhere in a templated class
|
||
derived from a virtual base class for execution when the event triggered
|
||
by the method fired, and returned a hashcode to the templated object for
|
||
the event to use when the event fired. The event gets the event handler
|
||
from the hashcode, and the virtual base class in the event handler fires
|
||
the lambda in the derived class, and the lambda works as a continuation,
|
||
operating in the context wherein it was defined, making event oriented
|
||
programming almost as intuitive as procedural programming.
|
||
|
||
But then we have a problem, because we would like to store event
|
||
handlers in the database, and restore them when program restarts, which
|
||
requires pod event handlers, or event handlers constructible from POD
|
||
data, which a lambda is not.
|
||
|
||
We could always have some event handlers which are inherently not POD
|
||
and are never sent to a database, while other event handlers are, but
|
||
this violates the dry design principle. To do full on functional
|
||
programming, use std::function and std::bind, which can encapsulate
|
||
lambdas and functors, but are slow because of dynamic allocation
|
||
|
||
C++ does not play well with functional programming. Most of the time you
|
||
can do what you want with lambdas and functors, using a pod class that
|
||
defines operator(\...)
|
||
|
||
# auto and decltype(variable)
|
||
|
||
In good c++, a tremendous amount of code behavior is specified by type
|
||
information, often rather complex type information, and the more one’s
|
||
code description is in types, the better.
|
||
|
||
But specifying types everywhere violates the dry principle, hence,
|
||
wherever possible, use auto and decltype(variable) to avoid redundant
|
||
and repeated type information. Wherever you can use an auto or a
|
||
decltype for a type, use it.
|
||
|
||
In good event oriented code, events are not triggered procedurally, but
|
||
by type information or data structures, and they are not handled
|
||
procedurally, as by defining a lambda, but by defining a derived type.
|
||
|
||
# Variable length Data Structures
|
||
|
||
C++ just does not handle them well, except you embed a vector in them,
|
||
which can result in messy reallocations.
|
||
|
||
One way is to drop back into old style C, and tell C++ not to fuck
|
||
around.
|
||
|
||
struct Packet
|
||
{
|
||
unsigned int bytelength;
|
||
unsigned int data[];
|
||
|
||
private:
|
||
// Will cause compiler error if you misuse this struct
|
||
void Packet(const Packet&);
|
||
void operator=(const Packet&);
|
||
};
|
||
Packet* CreatePacket(unsigned int length)
|
||
{
|
||
Packet *output = (Packet*) malloc((length+1)*sizeof(Packet));
|
||
output->bytelength = length;
|
||
return output;
|
||
}
|
||
|
||
Another solution is to work around C++’s inability to handle variable
|
||
sized objects by fixing your hash function to handle disconnected data.
|
||
|
||
# for_each
|
||
|
||
template<class InputIterator, class Function>
|
||
Function for_each(InputIterator first, InputIterator last, Function fn){
|
||
while (first!=last) {
|
||
fn (*first);
|
||
++first;
|
||
}
|
||
return move(fn);
|
||
}
|
||
|
||
# Range-based for loop
|
||
|
||
for(auto x: temporary_with_begin_and_end_members{ code;}
|
||
for(auto& x: temporary_with_begin_and_end_members{ code;}
|
||
for(auto&& x: temporary_with_begin_and_end_members{ code;}
|
||
for (T thing = foo(); auto& x : thing.items()) { code; }
|
||
|
||
The types of the begin_expr and the end_expr do not have to be the same,
|
||
and in fact the type of the end_expr does not have to be an iterator: it
|
||
just needs to be able to be compared for inequality with one. This makes
|
||
it possible to delimit a range by a predicate (e.g. “the iterator
|
||
points at a null character”).
|
||
|
||
If range_expression is an expression of a class type C that has both a
|
||
member named begin and a member named end (regardless of the type or
|
||
accessibility of such member), then begin_expr is \_\_range.begin() and
|
||
end_expr is \_\_range.end();
|
||
|
||
for (T thing = foo(); auto x : thing.items()) { code; }
|
||
|
||
Produces code equivalent to:
|
||
|
||
T thing = foo();
|
||
auto bar = thing.items();
|
||
auto enditer = bar.end;
|
||
for (auto iter = bar.begin(); iter != enditer; ++iter) {
|
||
x = *iter;
|
||
code;
|
||
}
|