opaque_ptr and unique_ptr
Techniques for developing cross platform code using C++ and Objective C++
Today I'll introduce one of the simplest techniques for keeping your C++ code clean and portable using a trivial extension to std::unique_ptr
that I call opaque_ptr
.
std::unique_ptr
is a smart pointer class for managing uniquely owned resources. By default, it manages uniquely owned heap objects and frees them by calling delete
(or delete[]
), but the unique_ptr
class actually has 2 template parameters and also has a constructor with two parameters. The second type parameter is the type of the deleter, and the second constructor parameter is an instance of the deleter. The deleter is the code responsible for releasing the pointed-to object when the unique_ptr
is destroyed.
By using a custom deleter, you can use unique_ptr
to manage different kinds of resource allocated in different ways. For example, you can use unique_ptr
and a custom deleter to manage the lifetimes of SQLite objects like sqlite3_stmt
, or CoreGraphics objects like CGImage
.
Even if you wish to use heap-allocated objects, unique_ptr
's default deleter has a specific behavior that means that you may wish to replace it: it requires the pointed-to type to be a complete type in any context where the deleter is called (which happens in the destructor, move assignment operator, and reset
member function of unique_ptr
). This means that the default deleter can get in the way of the opaque pointer or PIMPL idiom which we often want to use when writing Objective C++ code (because PIMPL allows us to encapsulate Objective C++ so that most of our code is pure, portable C++).
But don't worry. We can use PIMPL and keep resource management simple by defining opaque_ptr
like this:
template <typename T> using opaque_ptr = std::unique_ptr<T, std::function<void(void*)>>; template <class T, class... Args> inline opaque_ptr<T> make_opaque(Args&&... args) { return opaque_ptr<T>(new T(std::forward<Args>(args)...), [](void* p){ delete static_cast<T*>(p); }); }
This code defines opaque_ptr
as a partial specialization of unique_ptr
(and adds a trivial make_opaque
function that allows you to create and initialize a lifetime-managed instance of T
using new
and delete
for allocation on the standard C++ heap).
opaque_ptr
is a unique_ptr
so it has the same characteristics as unique_ptr
(it automatically manages the lifetime of a resource), but it also makes available an additional capability that can be important when hiding implementation details.
As described earlier, when you use unique_ptr
with the default deleter, the pointed-to type must be a complete type in any context where the deleter is called. When you use opaque_pointer
, the pointed-to type only needs to be complete in the context where the deleter function is created. Indeed, if the custom deleter delegates to code such as a C API, you may never need the complete type at all.
This means that by using opaque_ptr
you can provide a header file with a simple declaration naming the type of the managed resource and completely hide the implementation:
struct resource; struct utility { opaque_ptr<resource> thing; }; utility u;
This code won't compile if you replace opaque_ptr
with unique_ptr
.
This information hiding aspect is particularly useful when dealing with multi-language code; for example, it lets us keep Objective C details hidden from pure C++ code. In the example above, the resource
could be an object created from Objective C++ code that contains Objective C objects as members, but the C++ code that uses the utility
class does not need to (and cannot) know anything about the implementation of the resource. In addition, we can change the allocation and deallocation method for resource
without needing to recompile clients of the utility
class (and without having to create out-of-line definitions for the move constructor and destructor of utility
).
In summary:
opaque_ptr
makes the standard PIMPL idiom easier to use because you don't have to declare and define a separate move constructor and destructoropaque_ptr
can be used to manage the lifetimes of resources provided by C APIs such as SQLiteopaque_ptr
can be used for lifetime management while keeping Objective C++ implementation details hidden from pure C++ code
Transparent Type Declarations for Mutable Objects in Objective C
Prefer specific return types for low-level functions returning mutable objects
Yesterday, I made the claim that it was appropriate for a function that created an NSMutableArray
to return it directly from a function with return type NSArray*
. That was based on this thinking:
- The creating function doesn't keep a pointer to the array. The caller of the function becomes the sole owner of the array so no one is going to accidentally mutate it.
- Memory overhead of
NSMutableArray
versus immutableNSArray
is not significant - Cocoa doesn't provide an efficient way to move memory between
NSMutableArray
and an immutableNSArray
- Limiting the interface of the function to the minimum required preserves flexibility in the implementation of the function
- Using the immutable
NSArray
interface as the return type for the function encourages good habits in the caller - promoting programming with immutable objects in other parts of the codebase
But here's some considerations that I feel I undervalued:
- The cost of copying an immutable array versus an
NSMutableArray
is radically different: immutable array copy is constant time since it just has to retain the object; copying anNSMutableArray
actually copies the data - Objective C idiom suggests that properties for mutable/immutable objects should be marked and implemented as copying
In that environment, returning an NSMutableArray
as an NSArray*
seems misleading. An array is likely to see at least one copy and not knowing how expensive that copy will be makes it hard for the caller to do the right thing.
Given this, I think it makes sense for low-level functions that create and return NSMutableArray
objects to show NSMutableArray*
as the return type.
Similarly, if the goal is to provide higher-level functionality and an immutable API that returns NSArray*
then it seems appropriate for the implementation to return an immutable NSArray
that can be copied in constant time.
Fibonacci Comedy
I'm available for birthdays, birthdays, weddings, and conferences
I did some Fibonacci jokes at an Open Mic night. The club owner said my set was interminable.
I said “But I’m improving. Each Fibonacci joke I write is funnier than the previous one.”
She said “Fibonacci jokes are all recycled. Give me a new Fibonacci joke that isn’t just a rehash of a couple of others.”
I said “All comedians build on previous work, it’s just more obvious with Fibonacci comedy”. She said “It seems formulaic.”
She said “I’ll give your Fibonacci jokes one more chance. The last joke’s always the biggest so show me how you’ll close your set.”
I said “I don’t have a closer for my Fibonacci comedy set. But if I did, it’d be funnier than any Fibonacci joke you’ve ever heard.”
She said “Truth is, you need more jokes. I had a Fibonacci comedian here last week that had two jokes for every joke in your set.”
She said “That Fibonacci comedian we had last week was great. Some of his stuff was prime material.“
I said “I bet that Fibonacci comedian didn’t have more than two original jokes in his whole set.”
She owned a comedy club. I was just an amateur comedian. With each Fibonacci joke I told, I could feel the gap widening between us.
I told one of my jokes to my mum. She said “This is the second Fibonacci joke I've heard today and it's exactly the same as the first one.“
1-1-2-3-5-8. Who do we appreciate? Fibonacci!
Javascript splatting
Some quick notes about spread and rest
Splatting in Javascript uses the spread operator (prefix ...
) and rest parameters (also prefix ...
) and enables many of the same techniques as splatting in Python.
You can expand an array into multiple arguments using the spread operator:
function show3(a, b, c) { return [a, b, c].join(" "); } var values = ["a", "simple", "example", "unused"]; show3(...values); < "a simple example"
You can combine multiple arguments into a single array using a rest parameter:
function showAll(...args) { return args.join(" "); } showAll("a", "simple", "example"); < "a simple example"
You can also gather the trailing elements of an array into a new array using a rest operator inside an array destructuring pattern:
var [a, b, ...others] = values;
And you can expand an array into multiple elements using the spread operator inside an array literal:
var moreValues = ["before", ...values, "after"];
In fact, spread works with any object that is iterable (including generators):
function* generateValues() { yield "A"; yield "SIMPLE"; yield "EXAMPLE"; } var generatedValues = [...generateValues()]; show3(...generateValues()); < "A SIMPLE EXAMPLE"
All of this syntax is enabled right now in Chrome and is just part of the destructuring syntax introduced with ES6.
Note that future versions of Javascript will likely use spread and rest in further contexts. For example, property spread will allow injecting an object's properties into an object literal and property rest will allow gathering an object's remaining properties into a new object when destructuring using an object pattern.
Also note that while Javascript does have an easy way to yield each item of an array (or other iterable) from a generator without writing a loop, it does not use the spread operator; instead, you must use yield*
.
// function* generateValues() { yield ...values; } // This doesn't work function* generateValues() { yield* values; } // Use this instead
Splatting in Swift
Swift doesn't provide splatting, but we won't let that stop us
If you have a 3-tuple and a function with 3 parameters, you might want to pass the tuple members as arguments to the function. Some languages make this easy by providing a scatter or splat operator that decomposes a tuple into multiple arguments. Python uses prefix * for this. You can place this operator in front of a tuple that you're passing to a function to use each tuple member as a separate argument. Python's great. (It also provides a gather operator that combines multiple arguments into a single tuple using similar syntax, but applied at the function definition). But what about Swift?
In this article, I'll just look at splatting as a scatter operation from a tuple to multiple arguments.
Swift was originally created with syntax-free splatting: you could pass a tuple to a function expecting multiple arguments. The tuple members would be expanded into arguments and the function call would succeed. For predictable reasons that feature has been removed from Swift, but less predictably no other splatting feature has yet taken its place. Let's see what we can do.
Let's start with a couple of tuples and a couple of functions:
let t = (1, 2, 3) // Anonymous tuple let n = (x: 1, y: 2, z: 3) // Named tuple func fn(_ a : Int, _ b : Int, _ c : Int) { // Separate params print(a, b, c) } func tfn(_ t: (a : Int, b : Int, c : Int)) { // Single param print(t.a, t.b, t.c) }
We have one anonymous 3-tuple where the members are not named and we have another tuple with the same types and arity, but using the names x, y, and z for the members. And we have two functions similar to our tuples. One function takes 3 arguments, and the other takes a single argument that is a 3-tuple. Note that in both functions we are able to refer to the data using names. Note also that the names used by the functions (a, b, and c) do not match the names used by the named tuple (x, y, and z).
Now we're going to pass these tuples to these functions.
First, we'll use the multiple argument function and just name the members of the tuple:
fn(t.0, t.1, t.2) fn(n.0, n.1, n.2) fn(n.x, n.y, n.z)
It's easy enough.
Now let's try passing the tuples to the function that takes a tuple:
tfn(t) // OK tfn(n) // Fails!
Calling the single argument function with the anonymous tuple is easy, but the named tuple doesn't match the names used by the function, so the compiler gives us an error. This is easily fixed: we know that anonymous tuples work. Let's just create a function to anonymize our tuple and use it:
func anon <T, U, V>(t: (T,U,V))->(T,U,V) { return (t.0, t.1, t.2) } tfn(anon(n))
It works! We can add overloads of anon
for any tuple arity that we care about.
Now we know how to pass named and anonymous tuples to functions that expect a tuple and how to pass tuple members to functions that expect individual arguments, let's get to splatting: we want to pass our tuples to the multi-parameter function without writing each member by hand.
We'll start with a simple function:
func splat<T, U, V, R>(_ t : (T,U,V), _ fn: (T,U,V)->R)->R { return fn(t.0, t.1, t.2) } splat(t, fn) splat(n, fn)
Again we can overload this for any arity we care about and it handles named and anonymous tuples automatically. Let's improve the syntax:
infix operator ->* { associativity left } func ->* <T, U, V, R>(_ t : (T,U,V), _ fn: (T,U,V)->R)->R { return fn(t.0, t.1, t.2) } t ->* fn n ->* fn t ->* { (a, b, c) in print(a, b, c) } n ->* { (a, b, c) in print(a, b, c) }
Works great with closures too, but maybe we can make it look more like a regular function call?
prefix operator * { } prefix func * <T, U, V, R>(_ fn: (T,U,V)->R)->((T,U,V))->R { return { t in fn(t.0, t.1, t.2) } } (*fn)(t) (*fn)(n)
There it is! We've successfully defined a splat operator that lets you pass a tuple of the correct types and arity to a function with multiple parameters. We can define overloads of other arities to complete the job. Unlike Python's splat, we've had to apply the operator to the function and not the tuple, but this is probably the best we can do without compiler support.
Protected Swift
Swift doesn't have protected
, but we can emulate it
Other languages (including C++ and C#) provide an access level called protected
. When applied to a method, protected
means that a derived class can override and call that method, but code outside of the inheritance hierarchy cannot call the method. Swift does not provide this access level, but we can emulate it by using the public
and private
access levels that Swift does have.
So how are we going to do this? Well, have you ever noticed that you can't call a method unless you provide all the required arguments? Sure you have. All we need is some kind of data type that the base class can create and other code cannot, we'll give our protected methods a parameter of this data type, and our job will be done. Only the base class will be able to create the special data. Only code that has access to the special data will be able to call the protected methods.
There are two ways that we can define a type that only the base class can create: we can define a private
type or we can create a public
type with a private
initializer. Only the second way is going to work for us since we need to use this type in public
methods, so here's the kernel of our magic:
public struct Overridable { private init() {} }
That's the core of our access control technique: a public
type with a private
initializer can be used in a public method, but can only be created by code in the same file as the type definition.
So this is what we do when we want to define a method that can be overridden, but not called: we define the base class, nest the Overridable
type inside it, and add a Overridable
parameter to the method, and pass an instance of this type when we call the method.
public class Base {
public struct Overridable {
private init() {}
}
public func layout(key: Overridable) {
}
private func updateLayout() {
layout(Overridable())
}
}
In the example above, you can see the layout
method is protected. Derived classes can override it, and they can call the base class method from their layout
implementation, but no code outside the base class can call the function directly because it can't create an instance of the Overridable
type.
But we're not quite done. What about the broader meaning of protected
where derived classes can call a method, but code outside the class hierarchy can't? We can use the same technique there too! The only difference is that we want to pass an instance of our special type down to derived classes.
Here's the code:
public class Base {
public struct Protected {
private init() {}
}
// ...
public func getSecrets(key: Protected)->Secrets {
return secrets
}
public init(_ proof: (Protected)->()) {
proof(Protected())
}
}
In this case, we still pass a special parameter to our protected method (getSecrets
), but we use a different type called Protected
, and the big difference is that the base class takes a function in its initializer to which it passes an instance of Protected
. Derived classes that call this initializer get the key that they need to call protected methods through this function.
Note that in neither case do we do any runtime checks. We rely on the Swift compiler to ensure that data must be created and initialized before being passed to these functions.
With this technique we can provide two kinds of method:
- Methods that can be overridden but not called
- Methods that can only be called by a derived class (and also overridden)
You can see slightly fleshier example code here: access-control.swift (GitHub Gist)
Swift Return Type Deduction
Swift has return type deduction for closures, but not for funcs
C++ has return type deduction for both lambdas and regular functions. Swift does not. This turns out to be pretty annoying in Swift. Suppose you want to create a function that returns a tuple of property values so you can use it for equality, hashing, or ordering. Well if you want to create a function to wrap the tuple creation (so that you actually use the same properties for all objects instead of just hoping that you're using the same properties), you then have to name all the types. Crazy, right? There's a kind of workaround: instead of creating a func
, create a data member that you initialize with a closure. Since closures have return type deduction and data has type deduction too, you don't have to name the types. The downside is that your closure has to take a parameter for your object: you can't capture self
in your closure during initialization. That means that the call syntax for accessing the function can't look like a regular method call.
class X { let a = 1 // Contributes to equality let b = 2 // Contributes to equality let c = 3 // Not part of equality static let bag = { (self: X) in return (self.a, self.b) } } func == (_ lhs: X, _ rhs: X)->Bool { return X.bag(lhs) == X.bag(rhs) }
C# Runtime Code Generation
C# offers a number of choices for runtime code generation
C# provides a number of options for runtime code generation. Before using any of them, you should think about whether you really need them: closures/lambdas or dynamic
provide powerful alternatives. If you do decide you really need to generate code at runtime, you've got at least three choices:
I've listed them in the order in which I think you should consider them.
Expression<T>.Compile strikes a good balance between ease of writing/debugging code and performance. You write a lambda as regular C# code and assign it to an Expression<T> variable. This gives you a tree at runtime that represents the code you wrote. You then transform the tree at runtime to match the code you want to output before calling Compile on it to generate a delegate.
With CSharpCodeProvider, you generate your dynamic code as text then pass it to the code provider to compile. The advantage is that it's super easy to debug and you're not limited in the kind of code you can generate. The disadvantage is that it's slower to compile this way. Even still, the C# compiler is super fast so don't skip this option because you're worried about performance until you've actually tested it against your performance budget.
Reflection.Emit is the hardest to use of these techniques. Instead of writing C# code, you use a rather convoluted API to generate IL opcodes. It's not fun to write, it's not fun to debug, and it's not fun to maintain the code. But if you really need to write .NET code independent of any language, this is a way to do it.
(There are also alternatives using code DOMs, Roslyn, and other tree structures to generate code to compile. Feel free to research Roslyn and C# scripting for more info).
NOTE: Runtime code generation works great on classic desktop Windows, but may not be available in your particular environment. If you need to add some smarts to your ahead of time compilation, you can generate C# source code as text and compile with your regular build tools. You don't need to use any of these output techniques for that. In that situation, you may want to use System.Reflection or Roslyn on the input side to look at your binaries or code to help you decide what code you want to generate.
EII (Everything is Initialization) or Callionica's Last Theorem
RA is I but so is E
RAII (Resource Acquisition Is Initialization) is a very Googlable initialism familiar to C++ programmers everywhere, but it's not just resource acquisition that's initialization in the world of C++. I have a truly marvellous description of VII (Validation Is Initialization), POEII (Preventing Overload Explosion Is Initialization), and TANMTFVII (Types Are Not Models, They're For Validation Is Initialization), but this blog is too small to contain it.
Having Trouble with C++ Templates?
Here's one weird trick that Seattle programmers don't want you to know about
If you're having trouble with templates in C++, here's one weird trick for making your life easier:
- Write your implementations as templates
- Put your implementations in an anonymous namespace (private API)
- Expose concretely typed wrappers in your named namespace that simply call through to your template implementations (public API)
- Only if you find yourself adding more and more wrappers for a specific template do you promote the template to the public API
This gets you ease of implementation and lets you declare your beliefs about how generic your implementation code is without allowing callers to make silly mistakes by passing the wrong types. Most implementation code could work with an infinite number of types, but if your particular app or library only uses a few types, only expose an API that deals with those few types. Every parameter to every public API is a dependency that your code is taking on the caller's code. You should minimize dependencies in the public API by preferring concrete types over template types where appropriate, even if you decide to use templates in your implementation.
A similar technique applies to code where you're the consumer: you don't have to call other people's templated code directly. If you know that only a few concrete types matter, create wrappers using the concrete types and call your wrappers. You'll never have to worry about sorting through long compiler errors again: you'll see clear error messages exactly at the point of failure.
Any Equality Will Do
Swift is missing convenient general-purpose equality
Sometimes you've got a bunch of values and objects that have all been shoved together in a container that obscures the type you started with. And it's useful to be able to compare these boxed values with other boxed values without a lot of fuss. In Swift, you can use Any
as the box, but Swift does not provide an easy way to compare Any
values for equality.
Here's some stupid Swift code that allows you to compare Any
boxes for equality. It returns false for objects with different dynamic type, uses reference equality (operator===) for reference types, uses value equality (operator==) for the known primitive types Bool, String, Int*, UInt*, Float*, and Double, does memberwise comparison for structs, tuples, collections, sets and dictionaries, compares dump output for enums, and returns false for everything else (if there is anything else). It's annoying stupid-looking code
This code was produced with some tips from Joe Groff who showed me how to avoid bridging conversions with a .dynamicType is T.Type
check and pointed out that dump
was successfully able to display the case of an enum.
Have a look at this Twitter thread if you're interested in comparing Any
values. There are other approaches in there that may be more appropriate if you are only dealing with a known, limited set of possible types.
NOTE: This code is only lightly tested! It probably fails in a million different ways.
// Dynamic cast without bridging func dynamicCast<T>(_ type: T.Type, _ v: Any)->T? { guard let result = v as? T where v.dynamicType is T.Type else { return nil; } return result } // Return a 2uple if both objects are convertible to type T, otherwise nil func matchCast<T>(_ type: T.Type, _ l: Any, _ r: Any)->(T, T)? { guard let l = dynamicCast(type, l) else { return nil; } guard let r = dynamicCast(type, r) else { return nil; } return (l, r) } // False for different dynamic types // Reference equality for objects // Operator == for known primitive types (Bool, String, Int*, UInt*, Float*, Double) // Tuples, structs, sets, collections, and dictionaries get memberwise comparison // Enums get a string comparison on their dump output // False for everything else func equal(_ l: Any, _ r: Any)->Bool { // Check if types are the same if (l.dynamicType != r.dynamicType) { return false } // Include this if you import Foundation // if let m = matchCast(NSNumber.self, l, r) { // return m.0 == m.1 // } // if let m = matchCast(NSString.self, l, r) { // return m.0 == m.1 // } // Must come after primitive tests if let m = matchCast(AnyObject.self, l, r) { return m.0 === m.1 // Reference equality } if let m = matchCast(String.self, l, r) { return m.0 == m.1 } if let m = matchCast(Bool.self, l, r) { return m.0 == m.1 } if let m = matchCast(Int.self, l, r) { return m.0 == m.1 } if let m = matchCast(Int8.self, l, r) { return m.0 == m.1 } if let m = matchCast(Int16.self, l, r) { return m.0 == m.1 } if let m = matchCast(Int32.self, l, r) { return m.0 == m.1 } if let m = matchCast(Int64.self, l, r) { return m.0 == m.1 } if let m = matchCast(UInt.self, l, r) { return m.0 == m.1 } if let m = matchCast(UInt8.self, l, r) { return m.0 == m.1 } if let m = matchCast(UInt16.self, l, r) { return m.0 == m.1 } if let m = matchCast(UInt32.self, l, r) { return m.0 == m.1 } if let m = matchCast(UInt64.self, l, r) { return m.0 == m.1 } if let m = matchCast(Float.self, l, r) { return m.0 == m.1 } if let m = matchCast(Float80.self, l, r) { return m.0 == m.1 } if let m = matchCast(Double.self, l, r) { return m.0 == m.1 } func equalCompound(_ l: Any, _ r : Any)->Bool { func isEnum(mirror: Mirror)->Bool { guard let ds = mirror.displayStyle else { return false; } return ds == .Enum } func equalDump(_ l: Any, _ r: Any)->Bool { var o1 = "" dump(l, &o1) var o2 = "" dump(r, &o2) return o1 == o2 } func isWorkingDisplayStyle(ds: Mirror.DisplayStyle?)->Bool { guard let ds = ds else { return false; } switch (ds) { case .Class: return false // Don't expect to use because we use ref equality case .Enum: return false // Can't use because case doesn't show up in mirror (but associated data does) case .Collection: return true // Looks OK case .Dictionary: return true // Looks OK case .Optional: return true // Looks OK - works because the cases have different number of children case .Set: return true // Looks OK case .Tuple: return true // Looks OK case .Struct: return true // Looks OK } } let m1 = Mirror(reflecting: l) let m2 = Mirror(reflecting: r) if (isEnum(m1) && isEnum(m2)) { // We can't get the case from the Mirror object or dynamicType // so we resort to using dump and comparing the strings return equalDump(l, r) } guard (isWorkingDisplayStyle(m1.displayStyle)) && (m1.displayStyle == m2.displayStyle) && (m1.children.count == m2.children.count) else { return false; } for (p1, p2) in zip(m1.children, m2.children) { if (!equal(p1.1, p2.1)) { return false; } } return true } return equalCompound(l, r) }
Code Generation
Swift code to produce a type switch to call an overloaded or generic function
Sometimes you need to realize a generic function so you can pass it as an argument. Sometimes you need to combine an overload set in to a single function so you can call it from an unconstrained generic context. This code generator helps either situation by producing a switch from the types you supply, with each case calling a function with a single name.
func codeGenOverload(_ fn : String, _ ret : String, _ types : [String]) { print("{ (v: Any)->\(ret) in ") print(" switch v {") for type in types { print(" case let v as \(type):") print(" return \(fn)(v)") } print(" default:") print(" fatalError(\"Unsupported type in type switch\")") print(" }") print("}") }} codeGenOverload("ex1", "()", ["String", "Int"])
The code above will generate a function like this:
{ (v: Any)->() in switch v { case let v as String: return ex1(v) case let v as Int: return ex1(v) default: fatalError("Unsupported type in type switch") } }
Code Generation
Swift code to copy properties from one object to another
If you need to copy properties of the same name from one object to another, you can use this code generator:
let props = ["name", "description", "contents"] for prop in props { print( "if local.\(prop) != remote.\(prop) {", " local.\(prop) = remote.\(prop)", "}", "", separator:"\n") }
Code Generation
Providing compile-time support for data
Brent Simmons has been writing an interesting series of articles about Swift in which he documents problems that Mac and iOS developers solve using the dynamic features of the Objective-C runtime.
They're well written (as usual) and show an interesting perspective, but they also mix a couple of concerns in together that I believe should be treated separately.
Here I'll just address the issue of providing compile-time support for data. In other words providing names and types that are known to the compiler and can be used in your code. Clearly this is a code generation problem: nothing you do at runtime will help you catch mistakes at compile time. You need to feed data to the compiler that the compiler will understand: we call that data "code". And you need to keep that data synchronized with the data format (schema) you'll be making available to your app.
There are 3 main choices:
- User writes the schema; tool generates the code
- User writes the code; tool generates the schema
- User writes a 3rd format; tool generates both code and schema
(Also a fourth choice: write code and schema separately and keep synchronized manually - not considered here)
There are pros and cons in the specifics of each of these choices, but what's common to all of them is that, to get compiler support, the code has to be available to the compiler at compile time. There is no runtime component to that support.
Brent's article offers both CoreData and DB5/OAAppearance with the implication that they use the dynamic addition of methods to provide compile-time support for data. But the compile-time checking is not enabled by the dynamic addition of methods! Compile-time checking is enabled by providing information about the data to the compiler in a format it understands: code. That's all.
This is important to understand, not just because it can help you comprehend some hypothetical tool that Apple will provide in the future (or the tools like CoreData that Apple provides today), but because it actually means that you can help yourself, now, without waiting.
Brent's position is that writing code to generate code is not a good answer, but I'm not totally sure what question he's asking. My position is that writing code to generate code is a good answer to the question "what do I do if I want compile-time support for my data and there are no existing tools that produce code for my language?".
Why is writing code to generate code a good answer to this question? Primarily because it gives good results and its easy. By "easy", I mean you can get valuable results with just a few hours work. Doesn't really get better than that does it?
The principles of "don't put off until runtime what you can do at compile time" and "don't repeat yourself" are still good ones. Code generation can help you with synchronizing code with external data formats and with factoring out common code that the language would otherwise require as boilerplate.
Just remember to check everything in to source control: source format, code-generating code, and generated code. And I recommend running the code generator when you make changes to the source format (rather than generating the code as a build step in your app). Also don't make your code generator spit out duplicate code that could be easily placed in one common function: code generation isn't an excuse to write garbage code; generated code, like all code, should be minimal.
About Me
I've written code professionally for iOS, Windows, and the web in C++, C#, Objective-C, Objective-C++, JavaScript, Python, and Visual Basic. I write most of my code in C++ because I run my own company and I get to choose how I balance performance, maintainability, cross-platform, aesthetic, and other concerns. I like C++. But I could easily have decided to write my apps in JavaScript or C# if the world was only slightly different. I used to work at Microsoft so code that I've worked on is currently running on hundreds of millions of machines.