Effective C++ by Scott Meyers is an amazingly good book. For me, it is to programming books like Watchmen is to comics – everything else I read, I feel disappointed that it isn’t as good. If you program C++ and you haven’t read it yet, get on it!
There are also several articles in the book that apply to any language – Item 28: Avoid returning “handles” to object internals, Item 32: Make sure public inheritance models “is-a.”, Item 53: Pay attention to compiler warnings. Even if you aren’t ever planning to write C++, maybe grab a friend’s copy and give it a skim.
There is a fair amount of material in there that is also applicable to Swift. Many features of modern C++ programming have fed into Swift – which isn’t surprising given Swift was born out of the LLVM team, and LLVM is written in C++. Here are a few items from the book, and how they might also apply to Swift.
edit: for the completists amongst you, this article gives a full rundown of every item in Effective C++ and how it relates to Swift. Note that many of the items are warnings on the dangers of C++, and for the most part these are all avoided in Swift.
Item 1: View C++ as a federation of languages.
Well, federation is a kinder word than hodgepodge. While Swift has been written from the ground up, so is a lot cleaner, it’s still similar to C++ in that it blends several styles (OO, generic, functional) together in one language, and you should be aware that different styles follow different conventions.
Items 3: Use const whenver possible
A welcome feature of Swift is the explicit distinction between let
(const) and var
(non-const) variable declarations. Using let
by default, and var
only when needed, is probably a good policy.
Swift also has the mutating
modifier that acts like the opposite of C++’s const member function qualifier (i.e. you must specify it if you want to be able to alter internal state, rather than if you are promising not to):
struct MyStruct { var val = 0 mutating func touch() -> Int { return ++val } func look() -> Int { return val } } let s = MyStruct() // s is a constant s.look() // fine - read-only s.touch() // compiler error
Sadly, that breaks down with classes, which don’t get to use the mutable keyword, so this compiles just fine:
class MyClass { var val: Int = 0 func touch() -> Int { return ++val } } struct MyStructWithClassProperty { var val = MyClass() } let s = MyStructWithClassProperty() s.val.touch()
This rules out using this feature to implement const-correct behaviour for more complex types (user-defined container classes, for example). Maybe someday it’ll be extended to cover more cases.
Item 5: Know what functions C++ silently writes and calls
Swift, like C++, silently writes some class functions for you. For example, if you don’t write an init()
function for a struct or base class, but you do give all its properties default values, it will write a default initializer for you that takes no parameters. But if you do write another initializer, that takes parameters, you have to write a default one as well.
Item 12: Copy all parts of an object
Beware the seductive struct
. It’s a value type, they say. It gets copied when you assign it, they say.
Well, to a point. As we saw in item 3, when you include a class as a property of a struct you leave some of those struct guarantees behind, and copying is one of them. If you were to make two copies of MyStructWithClassProperty
above, they will both end up pointing at the same instance of MyClass.
Unfortunately, unlike in C++, Swift structs don’t get to implement a copy constructor. When a copy is made, it’s a shallow bitwise copy and the author of the struct has no programmable hooks to find out it’s happening. Instead you have to implement a .copy() method of your own and hope users call it.1 Maybe someday struct copy constructors will be added as a feature.
Item 13: Use objects to manage resources
Classes in Swift can implement a deinit method, that executes when the class is destroyed after all references to it are deleted. And with ARC, when that happens is a lot more deterministic than it is in Java and garbage collection. Maybe you can get some of that sweet RAII action C++ programmers are so keen on.2 The Apple docs even encourage you to do this, with an example about putting gold coins back in a bank.
But be careful! Try typing the following into a playground:
class Person { init() { println("Object is being initialized") } deinit { println("Object is being deinitialized") } } func deinit_demo() { var reference1 = Person(name: "John Appleseed") } deinit_demo()
and watch as you see no deinitialization logged to the console. This is presumably because the playground grabs references to everything to keep them around for playground-purposes. Fair enough, don’t use this pattern in the playground then. But you also need to watch out for the variables being captured by any closures,3 which will keep them around just by touching them, not even needing to assign them to another variable. Remember this if you’re ever trying to debug some mystifyingly resource-leaking code.
“But what about structs?”, you ask. They’re created on the stack, surely they get destroyed as soon as they fall out of scope. Well, sure, except first they can’t have a deinit
method (again, maybe someday), and second, they can still be captured, so that’s no help.4
And finally, from More Effective C++:
Item 7: Never overload &&, ||, or ,
Or, in Swift’s case, totally go ahead and overload them, but make sure you do it right.
In C++, overloading ||
is dangerous because of the short-circuiting feature programmers are used to. Suppose you write the following:
if(cheapOp() || superExpensiveOp()) { doSomething() }
Say cheapOp()
returns true. That means the whole if
statement can never not be true, no matter what superExpensiveOp()
returns. So what’s the point of executing it? None, so in C, C++, Swift and most other languages with a ||
, it won’t even get executed. If cheapOp()
is true most of the time, that could give you a big performance benefit.
Except in C++, when you implement your own ||
operator, it’s just a regular old function, and all its parameters are fully evaluated before they are passed
The same would happen in Swift – but Swift has a feature, @auto_closure, that can be used to avoid that problem. Putting @auto_closure before a parameter means that statement gets wrapped in a closure for later execution.
So this:
if(cheapOp() || superExpensiveOp()) { doSomething() }
gets rewritten as this:
if(cheapOp() || { return superExpensiveOp() }) { doSomething() }
and in your implementation of the || operator you do the following
func ||(lhs: LogicValue, rhs: @auto_closure () -> LogicValue) -> Bool { // only if the left-hand side is false... if(!lhs) { // ...do you then need to execute the closure that wraps the second half if(!rhs()) { return false } } return true }
A lot of people are excited about @auto_closure, as it enables some interesting possibilities – for example, a conditional logger that only executes expensive to-string operations if the log-level is debug, or an implementation of the ruby ||=
assignment-if-nil idiom (read this article for more on that one). One of the reasons I’m excited about Swift is there are probably more of these to come as the language continues to evolve through it’s beta period.
- You could always talk about this issue as if it’s a deliberate feature of your collection class, like Apple does in the documentation for Swift Arrays. ↩
- Course, RAII isn’t nearly as useful when there are no exceptions waiting to pounce and pull the rug from under you at any moment, but still. ↩
- And obviously getting explicitly assigned to another variable, or being passed out of the function, but I assume you realize that. ↩
- When they are presumably whisked off to heap-land to live with the other reference-counted animals. Is Swift performing some secret autoboxing for this under the hood? ↩