C++ for Swift developers
Swift in a sense is very much like C++, and when I say C++ I mean C++11 and beyond. One could also say that Swift is cleaner C++, or C++ without the backwards compatibility baggage from the 80s. To give an idea here’s a minimal modern C++ code:
#include <iostream> // 1
using namespace std; // 2
auto main() -> int { // 3
cout << "Hello world!"; // 4
return 0; // 5
}
Now lets break them down from Swift perspective.
#include <iostream> // 1
Swift equivalent would be import iostream
. Frameworks usually come with an umbrella header as a single file, so typically we need to add only one include statement per framework like in Swift. But unlike Swift, we need to add include statements for every file we want to use from our own code.
#include <framework> // external framework
#include "path/to/file.h" // local file
using namespace std; // 2
Unlike Swift, C++ has a proper support for namespaces. So if two types in different frameworks have same type name they can be resolved using their namespaces. For example fwkA::JSON
and fwkB::JSON
. To get a more Swift like behavior we can add using
to indicate that the symbols do not need their namespace for every single usage.
auto main() -> int { // 3
// ...
}
This is the modern way of writing functions in C++. In classic fashion this function could also be written as:
int main() {
std::cout << "Hello world!";
return 0;
}
cout << "Hello world!"; // 4
Like Swift we can also override operators in C++. In this case the iostream
library provides cout
which is an object of type ostream
that outputs to standard output stream. iostream
also provides overrides for operator <<
for many common types like for string
that we’re using above. But it can also work for our custom types if we provide the operator <<
for our type. The Swift equivalent to this would be the CustomStringConvertible
protocol.
return 0; // 5
And finally since our function requires a return type int
we have to end our function with a return
statement.
With a basic introduction, now let us take a look at C++ in a bit more detail from Swift perspective.
Variables
Swift | C++ |
---|---|
|
|
Just like in Swift types can be deduced by the compiler using auto
, but if required we can always also provide the type information explicitly.
Swift | C++ |
---|---|
|
|
Unlike Swift, type conversions can also occur implicitly in C++. This needs to be carefully watched out for to avoid hard to find bugs. For other cases we need to explicitly convert data from a type to another. For example int
to string
Swift | C++ |
---|---|
|
|
String
String interpolation in C++ isn’t as good as with Swift. But nonetheless we can construct string
with either explicit string conversion, like we saw above
Swift | C++ |
---|---|
|
|
Or using ostringstream
which works just like cout
and makes use of operator <<
auto apples = 3;
ostringstream ss;
ss << "I have " << apples << " apples.";
auto str = ss.str();
which can then further be reduced to:
auto str = (ostringstream()<<"I have "<<apples<<" apples.").str();
Swift | C++ |
---|---|
|
|
Array
Swift Array
equivalent in C++ is vector
. Here’s how you use it
Swift | C++ |
---|---|
|
|
And this is how you iterate a vector
Swift | C++ |
---|---|
|
|
The &
here means that we wish to use item
as a reference. If we were to use auto item
it would always create a new copy.
Dictionary
Swift Dictionary
equivalent in C++ is map
. Here’s how you use it
Swift | C++ |
---|---|
|
|
And this is how you iterate over a map
Swift | C++ |
---|---|
|
|
Optional
Swift developers love Optional
types. Fortunately for us, C++ also got optional
types starting c++17. Although not as fancy as with Swift, but the core idea is the same: A type that either has the value or not. Here’s an example:
Swift | C++ |
---|---|
|
|
The *str
can be thought of as force unwrapping in Swift, str!
. And after dereferencing we can either use the .
or the handy ->
operator to access the variable.
(*str).size();
str->size();
Function
C++ functions do not have argument labels
Swift | C++ |
---|---|
|
|
But if you really miss having named arguments there are many tricks of all shapes and sizes out there. The simplest is to use a struct
for arguments
struct Args { string person, day; };
auto greet(Args args) -> string {
return (ostringstream()<<"Hello "<<args.person<<", today is "<<args.day<<".").str();
}
greet({.person = "John", .day = "Wednesday"})
Closures
C++ has a good support for closures. Here’s an example:
Swift | C++ |
---|---|
|
|
That said, C++ still doesn’t has all the fancy functional operations like map
, filter
, ...
that we get with Swift, but they’re coming soon with C++20 ranges. For now we can use transform
as an equivalent to Swift map
Swift | C++ |
---|---|
|
|
The C++ code here would actually mutate the numbers
. If we want the truly Swift map
equivalent we would have to create an empty vector
and append data to it
auto mappedNumbers = vector<int> {};
transform(numbers.begin(), numbers.end(), back_inserter(mappedNumbers), [](auto n) {
return 3 * n;
});
Classes
Unlike Swift in C++ there isn’t much difference between a struct
and a class
with the only difference being that a C++ struct has all members public
by default whereas a class
has all members as private
by default. Whether the instance is mutable or not depends on how it is declared.
Swift | C++ |
---|---|
|
|
Initializer (or constructor as they are called in C++) have a slightly different syntax
Swift | C++ |
---|---|
|
|
In C++ subclasses need to be more explicit than Swift.
Swift | C++ |
---|---|
|
|
In C++ methods are by default bound to the declared types. Lets look at an example to understand what I mean by that.
auto shape = make_shared<Shape>("My Shape"); // create new Shape
cout << shape->simpleDescription() << endl; // prints: A shape with 0 sides.
shape.reset(new Square(5.2, "My Square")); // release Shape and create new Square
cout << shape->simpleDescription() << endl; // prints: A shape with 4 sides.
In this case since shape
is originally declared as of type Shape
, so even after we assign it to a Square
type simpleDescription
still invokes Shape::simpleDescription
. To get the same behavior as Swift we would have to mark the base class simpleDescription()
as virtual
, so that if we assign a Square
instance to Shape
it would dynamically dispatch the Square::simpleDescription
at runtime.
class Shape {
virtual auto simpleDescription() -> string {
return (ostringstream()<<"A shape with "<<numberOfSides<<" sides.").str();
}
// ...
};
class Square: public Shape {
auto simpleDescription() -> string override {
return (ostringstream()<<"A square with sides of length "<<sideLength).str();
}
// ...
};
auto shape = make_shared<Shape>("My Shape"); // create new Shape
cout << shape->simpleDescription() << endl; // prints: A shape with 0 sides.
shape.reset(new Square(5.2, "My Square")); // release Shape and create new Square
cout << shape->simpleDescription() << endl; // prints: A square with sides of length 5.2
Reference counting
Swift has reference counting mechanism builtin. C++ also provides reference counting, but it isn’t builtin. In order to get the same behavior as Swift, we need to make use of shared_ptr
and weak_ptr
. Here’s how it looks in C++
class MyString {
public:
MyString(string str) : str(str) {
cout << "created" << endl;
}
~MyString() {
cout << "destroyed" << endl;
}
string str;
};
auto s0 = make_shared<MyString>("hello!");
cout << "first strong ref: " << s0->str << " refCount:" << s0.use_count() << endl;
auto w0 = weak_ptr<MyString>(s0);
cout << "first weak ref: " << s0->str << " refCount:" << s0.use_count() << endl;
auto s1 = s0;
cout << "second strong ref: " << s1->str << " refCount:" << s0.use_count() << endl;
And this is the output
created
first strong ref: hello! refCount:1
first weak ref: hello! refCount:1
second strong ref: hello! refCount:2
destroyed
No surprises here, as you can see only a single instance is ever created and both the instance refer to the same thing with just the reference count that gets incremented. Also weak reference doesn’t increment the reference count. And then finally when we’re done the instance gets automatically deallocated. Just like in swift.
Error handling
Error handling in C++ is a bit more involved than Swift. So if we want to see the C++ exception handling from the perspective of Swift error handling, it might look something like:
Swift | C++ |
---|---|
|
|
Unlike Swift, C++ functions are assumed to always throw
. If they’re guaranteed to not throw at all then we can mark them as noexcept
, in that case if any exception is thrown the app just crashes.
In Swift our PrinterError
has to conform to Error
, similarly in C++ our custom exception PrinterError
has to subclass exception
.
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
Implementing the PrinterError
type of exception
is a bit more involved since C++ enums are not as awesome as Swift, but we can improvise.
class PrinterError: public exception {
public:
static auto outOfPaper() -> PrinterError {
return PrinterError(0, "out of paper");
}
static auto noToner() -> PrinterError {
return PrinterError(1, "no toner");
}
static auto onFire() -> PrinterError {
return PrinterError(2, "on fire");
}
PrinterError(int errorCode, const std::string &message) noexcept
: errorCode(errorCode), message(message)
{}
auto what() const noexcept -> const char * {
return message.c_str();
}
int errorCode;
string message;
};
Generics
If there’s one thing where C++ beats Swift hands down, it’s Generics. Here’s an example how a generic class
might look in C++
template <typename Element>
class Stack {
public:
auto push(Element e) {
storage.push_back(e);
}
auto pop() -> optional<Element> {
if (storage.empty()) {
return nullopt;
}
auto lastEle = storage.back();
storage.pop_back();
return lastEle;
}
private:
vector<Element> storage;
};
When it comes to generics, C++ isn’t as as verbose as Swift.
Swift | C++ |
---|---|
|
|
Notice no type constraints to Sequence
or Equatable
since the C++ compiler automatically takes care of that. In case the type constraints fail, for instance T.Element
does not have ==
implemeted, the compiler throws errors that are generally hard to decipher. If you like that level of control c++20 has a proposal for constraints and concepts which is very close to where Swift is already at.
And finally, for a wrap up and to show how easy it is to work with C++ generics. Lets revisit our closure example from above where we tried to replicate the Swift map
behavior with transfrom
.
auto mappedNumbers = vector<int> {};
transform(numbers.begin(), numbers.end(), back_inserter(mappedNumbers), [](auto number) {
return 3 * result;
});
We can easily make this a generic function in our custom namespace
namespace wl {
template <typename Sequence, typename Func>
Sequence map(const Sequence & seq, const Func & func) {
Sequence output;
transform(seq.begin(), seq.end(), back_inserter(output), func);
return output;
}
}
auto numbers = vector<int> {20, 19, 7, 12};
auto mappedNumbers = wl::map(numbers, [](auto number) { return 3 * number; });