As the full c++11 feature set is rather extensive, we want to start the upgrade to c++11 with 3 main areas of focus:
- Lambdas
- Unrestricted Unions
- Move semantics
- Definition:
[capture-list] (params) mutable(optional) exception attribute -> ret { body }
-
What rule should we use for the capture-list?
-
Default capture by reference (&), specific capture by value (name of variable)
[&, n] () {...}
-
(CXX choice) Default capture by value (=), specific capture by reference (& name of variable)
[=, &n] () {...}
-
No default capture, specific capture by reference or value
[m, &n] () {...}
-
Should we mandate the return type, or only specify it if the compiler can no deduce it properly?
// Explicit return type. [] () -> bool { return true; } // Implicit return type. (**CXX choice**) [] () { return true; }
-
-
Should we allow auto capture of lambdas?
// Capture by type.
std::function<int(bool)> lambda = [](bool dummy) -> int { return 5; };
// Auto capture. (**CXX choice**)
auto lambda = [](bool dummy) -> int { return 5; };
- General style questions around lambdas:
- How do we align them?
- Do we wrap the braces { ... } ?
- Spacing between capture [capture] (parameters) attributes -> return type { body }?
- No support for generic lambdas until C++14 source: http://en.cppreference.com/w/cpp/language/lambda
- Old Way:
void _send() {
VLOG(2) << "Finished send";
}
void send() {
socket.send("Hello World!").then(lambda::bind(_send));
}
- New Way:
void send() {
socket.send("Hello World!").then(
[]() -> void {
VLOG(2) << "Finished send";
});
}
We can now have non-pod types in unions. This allows us to
- Overlap storage allocation for objects that never exist simultaneously:
#include <string>
struct T {
enum {Str, Int, Double, Float} kind;
T() : kind(Int) {}
union Data {
Data() : int_(5) {}
~Data() {}
std::string str;
int int_;
double double_;
float float_;
} data;
};
int main() {
printf("size of T = [%ld] bytes\n", sizeof(T));
}
output:
size of T = [16] bytes
- Avoid the requirement of a default constructed object if it is a member of the union:
#include <vector>
class Obj {
Obj(int _arg) : arg(_arg) {}
private:
Obj() = delete;
int arg;
};
struct T {
T() {}
~T() {}
union {
Obj obj;
};
};
int main() {
// This would complain about Obj not having a default constructor.
//std::vector<Obj> data(1);
// This works because the union only reserves space in T, it doesn't
// initialize obj.
// Now we've allocated space for an Obj, without forcing a default constructor.
std::vector<T> data(1);
}
- One of the concerns around using unrestricted unions is that they are tricky to get right. Specifically the control of construction and destruction of non-pod members is pushed on to the developer. This is error prone, and can cause subtle leaks. To deal with this we propose the following:
- Only use raw unrestricted unions in performance critical library code such as Option, Try, Future. The storage of these is not likely to change because they implement very concrete concepts.
- For non performance critical code use boost::variant. It is a little bit slower, and takes longer to compile; however, we believe this is a trade-off worth making for the safety introduced. boost::variant supports RAII and is much safer to use.
- This is a wonderful feature that C developers have implemented manually for many years. At its core this is about reducing expensive copies, at 30K feet this is about making ownership and lifetime expectancy relationships more clear.
- The STL incorporates this heavily:
std::string long_string("ABCDE...");
std::vector<std::string> data;
// This makes a copy of long_string and
// inserts it into the vector.
data.push_back(long_string);
// This moves the temporary string into
// the vector, swapping the data pointer,
// and size of the string, rather than
// copying the whole string.
data.push_back(std::string("ABCDE..."));
// Roughly Equivalent to:
{
data.push_back(std::string());
std::swap(long_string, data.back());
}
// This perfectly forwards the construction
// arguments. This means we don't even swap
// any pointers. The char pointer gets passed
// to the constructor of the string that has
// already been allocated inside the vector's
// data segment.
data.emplace_back("ABCDE...");
- For the class:
class T {
private:
std::vector<std::string> data;
};
- We can define a move constructor:
T(T&& other) {
std::swap(data, other.data);
}
- We can define a move assignment operator:
T& operator=(T&& other) {
if (this != &other) {
std::swap(data, other.data);
}
return *this;
}
- We can define a perfect forward constructor:
template <typename... Args>
T(Args&&... args) data(std::forward<Args>(args)...) {}