"Learning the fundamentals of a programming language is one thing; learning how to design and implement effective programs in that language is something else entirely. This is especially true of C++, a language boasting an uncommon range of power and expressiveness. Properly used, C++ can be a joy to work with. An enormous variety of designs can be directly expressed and efficiently implemented. A judiciously chosen and carefully crafted set of classes, functions, and templates can make application programming easy, intuitive, efficient, and nearly error-free. It isn't unduly difficult to write effective C++ programs, if you know how to do it. Used without discipline, however, C++ can lead to code that is incomprehensible, unmaintainable, inextensible, inefficient, and just plain wrong" - Scott Meyers, Effective C++, Third Edition
C++ is cross compiler, cross platform. Performance critical code is the bread and butter of C++. Conforming ISO C++.
Threads, atomics, futures, Lambda functions. C++ is very relevant for WinRT, GPGPU. C++ is the language for performance. C++M.
Variadic Templates are very difficult to implement in the compiler.
Writing code in C++ is clean and safe and fast.
clean: minimal lines of code or boilerplate. just like c#, javascript
safe: not fragile, no error codes, exception / type / memory safe
fast: c++
What has changed?
C++ is having a renaisance, because performance is back on the forefront. Excess power in CPU is now dropping off. We just have more cores. Kinect, Bigger experiences, data center (power distribution, ... safes a lot of money) efficiency. Mobile devices.
More and more people come back to c++
Core mindset:
Achieve C++’s value proposition of efficient abstraction.
Strong abstraction: Type-safe Object Oriented and generic code for modeling power, without sacrificing control and efficiency.
Full control over code and memory: You can always express what you want to do. nd you can always control memory and data layout exactly.
Pay-as-you-go efficiency: No mandatory overheads, don’t pay for what you don’t use. You can go in and tune the code, but only when necessary.
"The going word at Facebook is that <em>‘reasonably written C++ code just runs fast,’</em> which underscores the enormous effort spent at optimizing PHP and Java code. Paradoxically, C++ code is (may be) more difficult to write than in other languages, but efficient code is a lot easier." – Andrei Alexandrescu
Managed languages optimize for programmer productivity.
C++ optimizes for performance.
Use the right tool for the job.
old:
circle* p = new circle( 42 );
vector<shape*> vw = load_shapes();
for( vector<circle*>::iterator i = vw.begin(); i != vw.end(); ++i )
{
if( *i && **i == *p )
cout << **i << “ is a match\n”;
}
for( vector<circle*>::iterator i = vw.begin(); i != vw.end(); ++i )
{
delete *i;
}
delete p;
Not even exception safe. Code should be at least twice as long. Missing try/catch, __try/__finally
new:
auto p = make_shared<circle>( 42 );
vector<shared_ptr<shape>> vw = load_shapes();
for_each( begin(vw), end(vw), [&]( shared_ptr<circle>& s ) {
if( s && *s == *p )
cout << *s << “ is a match\n”;
});
auto: type inference, let the compiler infer the type for you. Like var in C#.
make_shared: prefer it over new. Use the standard reference counted smart pointer: shared pointers. No need to clean up the vector. No need for any deletes. Automatic lifetime management. Exception safe. T* => shared_ptr<T>, new -> make_shared.
for_each:
STL algorithm. Loop body is a lambda function. [&]. Now calling STL library functions are now a lot more useful. They used to be too difficult to write.
DELETE.
Not necessary any longer.
Stack lifetime.
Automatic Lifetime mangement: Efficient + Exception safe.
automatic variables: stack scope.
class widget {
private:
gadget g;
public:
void draw();
};
lifetime automatically tied to enclosing object. No leaks. Exception safe.
void f() {
widget w;
:::
w.draw();
:::
}
lifetime automatically tied to enclosing scope.
constructs w, including the w.g gadget member.
Automatic destruction and deallocation for w and w.g
Automatically exception safety, as if finally{ w.dispose(); w.g.dispose(); }
Heap Lifetime: standard smart pointer
class gadget;
class widget {
private:
shared_ptr<gadget> g;
};
shared ownership: still keeps gadget alive w/auto lifetime mgmt. No leak, exception safe.
class gadget {
private:
weak_ptr<widget> w;
}
use weak_ptr to break reference-count cycles.
Widget owns the gadget. Gadget doesn't own its widget.
unique_ptr<> : if you own it uniquely. Doesn't need a reference count.
class node {
vector<unique_ptr<node>> children;
node* parent;
:::
public:
node(node *parent_)
: parent(parent_)
{
children.push_back( new node(...) );
:::
}
};
node observes its parent. The raw pointer is perfectly fine.
plain "new" should immediately initialize another object that
owns it, usually a unique_ptr.
if/when performance optimization is needed, consider
well-encapsulated uses of owning *'s and delete (e.g. hidden inside objects).
Example: writing your own low-level data structure.
"C++ is the best language for garbage collection principally because it creates less garbage." --Bjarne Stroustrup
You don't create garbage.
Containers:
vector<string> v;
v.push_back("Geddy Lee");
vector: default container. Compact, efficient: cache-friendly, prefetcher-friendly.
- Array that resizes itself. Heap-based, always owns its objects, always exception safe. Always efficient with 0-per-element overhead. It's compact. It's most efficient and cache-friendly when traversing the elements. There is nothing not to like about it. It's the default, but not the only thing. Use what is appropriate.
Size is fixed?
array<string, 50> a;
fixed size vector: array. compact, efficient: cache-friendly, prefetcher friendly. Safe to use, plays nice with STL algorithms. No heap allocation necessary.
Associate containers:
map (tree) or unordered_map (hash)
map<string, string> phone;
phone[“Alex Lifeson”] = “+1 (416) 555-1212”;
multimap<string, string> phone;
phone[“Neil Peart”] = “+1 (416) 555-1212”;
phone[“Neil Peart”] = “+1 (905) 555-1234”;
unordered_map<string, string> phone;
phone[“Alex Lifeson”] = “+1 (416) 555-1212”;
unordered_multimap<string, string> phone;
phone[“Neil Peart”] = “+1 (416) 555-1212”;
phone[“Neil Peart”] = “+1 (905) 555-1234”;
map: begin to end, stays sorted. Guaranteed. O(log(n)) insert and delete.
hash: Guaranteed O(constant) insert and delete.
Loops:
old:
for (auto i= v.begin(); i!=v.end(); i++)
{
...
}
new:
for_each( begin(v), end(v), [](string& s) {
...
});
old:
auto i = v.begin();
for( ; i != v.end(); ++i) {
if( *i > x && *i <y) break;
}
new:
auto i = find_if( begin(v), end(v),
[=](int i){ return i > x && i < y; }
);
always use non-member begin(), end().
Algorithms:
for_each: default traversal algorithm.
also transform for not-in-place semantics.
find_if: the default search algorithm
default binary sorting and searching.
sort, lower_bound, et al.:
use the same predicate for sort and search. Otherwise you get the wrong result.
use strict <, prefer named lambdas.
auto comp = [](const widget& w1, const widget& w2) {
return w1.weight() < w2.weight();
};
sort( v.begin(), v.end(), comp );
auto i = lower_bound( v.begin(), v.end(), comp);
clean, fast, safe.
Value types & reference types
"Copyable" Value Types (Default)
class point {
int x;
int y;
public:
:::
:::
};
Default in C++. All about memory and layout control.
"Polymorphic" Reference Types
class shape: boost::noncopyable{
public:
virtual draw();
:::
}
class circle: shape {
public:
virtual draw() override;
:::
}
all about base classes and virtual functions.
explicitly disable copying
disabling the copying is inherited.
explicit override control.
Value types and Move efficiency:
set<widget> load_huge_data(){
set<widget> ret;
// ... load data and populate ret...
return ret;
}
widgets = load_huge_data();
efficient, no deep copy.
no need for "heap allocation + return a pointer" workaround.
Just steal it's gut. It is an "organ donor". That set of widgets is out of scope anyway. The return semantics is normally copy, but now you get move.
vector<string> v = IfIHadAMillionStrings();
v.insert( begin(v) + v.size()/2, "tom");
v.insert( begin(v) + v.size()/2, "richard");
v.insert( begin(v) + v.size()/2, "harry");
efficient, no deep copy-shuffle.
(just 1.5M ptr/len assignments).
HugeMatrix operator+(
const HugeMatrix&,
const HugeMatrix&
);
hm3 = hm1 + hm2;
efficient, no extra copies.
Move is an optimization of Copy for value types.
Enable Move For Appropriate Value Types
class my_class {
unique_ptr<BigHugeData> data;
public:
my_class( my_class&& other) // move construction
: data( move (other.data)) {}
my_class& operator=( my_class&& other )
{ data = move( other.data); } // move assignment
:::
void method() {
if (!data) throw "moved-from object"; // check (if appropriate)
}
}
Copy => Move: Also enable move
If it can be cheaper than a deep copy.
Move != Copy: Some non-value types are naturally move-only. Example: unique_ptr.