TUTORIALUnfinished C++11 'guide'-ish

Posts 1–3 of 3 · Page 1 of 1
Unfinished C++11 'guide'-ish
How to write good/effective C++11?
NOTE: This contains C++11/14/17, you are expected to have some knowledge of C++ syntax
& Computer architecture (Bytes, pointers, references).

C++11 provides optimization abilities if you know how to
utilize them.
Most of C++11's features can be seen put to use in the STL library (Standard template library)
But, note that the STL library has very hard to read syntax for anyone not familiar with templates & universal references.



 
Chapter 1


Constness
To be able to fully understand the type deduction system/rules
you need to understand the rules of const types & references/pointers, if the X's type is
const (and the deducted type is not a reference to X) the constness will be removed.
However when creating a reference to X the constness cannot be removed because
it would violate the rules of constness.
When it comes to pointers, most people would assume:
Code:
const int* ptr;
Would mean that the value of 'ptr' is const, but this is wrong, the value of ptr
can be changed at any time, but ptr points to a const int meaning it is read only:
Code:
*ptr = 5
Would not compile (Error: Expression must be a modifiable Lvalue)
(If you would like ptr to be a const pointer you must do: const int* const ptr)


Basic Type Deduction

In C++98 (C++ I most belive you were familiar with) the
type deduction system was limited to templates, however
in C++11 we have auto, decltype and alot more ways for you to write
less code. This is possible because the compiler is able to 'detect'
what X return type is and determine the return type at compile time, meaning
creating variables using auto does not change code generation or similiar, it just
makes it easier to read/write the actual code.
To write poet like code you should always use the auto keyword when possible.

Take a look at this code:
Code:
int x = 5;
int& xx = x;
const int& xxx = xx;
const int xxxx = x;

auto y = x;
// The type of'y' is equal to int
// Type deduction looks at the declaration of 'x' and determines it is a int.

auto yy = xx; 
// The type of 'yy' is equal to int.
// Notice how using auto on a type of T& only 
// the type T. This is very usefull because alot of the 
// you want yy to get copied instead of using its 
// However you can still easily make it a reference by doing auto&.

auto yyy = xxx; 
// Type of 'xxx' is equal to 
// The deduction of const T& only 
// the type T. (Its constness is 
// The method to make it a reference is still the same.

auto yyyy = xxxx; 
// Type of 'yyyy' is equal to int. (Notice how the consness is removed)

auto& xy = xxxx; 
// Type of 'xy' is equal to const 
// Notice how the consness is kept, 
// the rules of const is taking over, you cannot create 
// non-const reference to a const type (Unless using const_cast which is a whole other discussion).
Notice how the constness is stripped from all auto's that are not references
to a const lvalue.

Type deduction on const & pointers:
Code:
int x = 5;
int* xx = &x;
const int* xxx = xx;

auto v1 = x; 
// v1 = int (Auto deduction is int.)

const auto& v2 = xx; 
// v2 = int*const& (A reference to a pointer that points to a const 
// auto deduction = int*

auto v3 = xxx; 
// v3 = const int* (A pointer to a const 
// Notice that the pointer points to a const int, the 
// is not const, its what it points to. (Meaning you can assign the pointer to a new ptr)


const auto* const v4 = v3; 
// v4 = const int* const (A const pointer to a const 
// auto deduction = const 
// now the pointer is const, you can no longer change the address it points to.

// When using auto for parameters the constness of the parameters
// is ignored if the parameters is a pointer, however if it is a reference 
// the constness over rules.
// e.g:

(const* int param1, const int* const param2, const int& param3)
{
        auto v1 = param1; // v1 = const int*
    auto v2 = param2; // v2 = const int* (param2's constness ignored)
    auto v3 = param3; // v3 = int (Constness & referenceness is ignored)
    auto& v4 = param3; // v4 = const int&
}



That is the basics of type deduction, there is alot more to cover
once you learn about Lvalues & Rvalues.

Lets write some code from what we've learned!
Code:
// Create a stl vector of size 15
std::vector<int> vec{ 15 };

// An example of minimizing loop code greatly
// is this:
for (auto it = vec.begin(); it != vec.end(); ++it)
    *it = rand();

// Instead of:
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it);
Obviously this doesn't change code generation at all, but it improves readability.


Decltype(auto) type deduction
You might think decltype(auto) & auto are the same thing, but they are not.
The reason as to why I started with talking about C++98's template deduction is because
auto uses C++'s template deduction rules for deducing the desired type.
The deduction rules for decltype(auto) is significantly different from auto's deduction rules.
Here are some of the differences:
Code:
decltype(auto) x = y; 
// y = int. x would be = int&
// Whilst using auto x would = int.

// This will cause some problems if you dont know the rules,
// this could messup a whole program if you do this in a library you create or similiar.
Here is an example of a function where it should return a non-reference:
Code:
auto lookupValue(const int& lookingfor)
{
      auto vector = getVector();
      // Do some computing to find the value you are looking for...    
      return vector[idx]; // Returns the value that it compared to
}
  • lookupValue returns a integer copy, as expected.
  • decltype(auto) would return a reference to the element in the vector, this is dangerous!


An example where it should be used:
Code:
decltype(auto) get(const int& idx)
{
    auto vector = getVector();
    return vector[idx];
}
It may be expected that a get function returns a reference to
the element in question (e.g when creating a vector class you have at/operator[] method that return T&)


We will talk more about type deducting in later chapters (As they are vital parts for understanding Rvalues/Lvalues & Move semantics)



 
Chapter 2

Rvalues & Lvalues, Whats the difference?
Rvalues & its use is a bit hard to grasp at first, which is why I will go over
them slowly and what they're used for, how to use them yourself, and hopefully
by the end you will have an understanding of what the difference between
RValues & Lvalues is, what each type is usefull for, and how to utilize them.
So, lets start shall we?

Lvalues are objects that are stored (They're life time extends past the current statement),
they have an address (You can use the Address-of operator on them).
Rvalues are values/objects that are not stored (They're life time do NOT extend past the current statement),
you cannot use the address-of operator on them.
If that was not clear, here is an example:
Code:
int x = 5; // Lvalue, stored untill the end of the current scope
int y = 7; // Same as 'x'
&x; // Totally valid line.
&y; // Same

x + y; // Rvalue, the result of x + y gets destroyed at the semicolon.
x + y = 10; // Invalid line, rvalue on the left hand side of an assignment.
int z = x + y; // Valid, rvalue on the right hand sife of an assignment.
45; // Rvalue, gets destroyed at semicolon.
You should now have a basic understanding of what Rvalues/Lvalues are, and the
difference between them.


Move Semantics & Perfect Forwarding
Move Semantics & Perfect forwarding are almost always
used together, these features must be implemented by yourself
as the programmer. (But the STL provides functions for making it easier & possible)
Lets start with Move semantics, as Perfect forwarding comes in the picture later.
Move semantics should be used for every type you create (class), especially ones
that are copy heavy (E.g takes up alot of memory, multiple inner vectors & or pointers etc).
What move semantics does is take advantage of Rvalues, you remember how rvalues were
destroyed once they reach the end of the statement, well. Since its gonna be destroyed anyway,
why not gut it first? (swap its components with yours), essentially moving the memory from the rvalue
into the current instance of the class.

First lets go over some of the functions in the STL related to move semantics.
std::move(lvalue);
std::forward<T>(l/r value);

The simplest way to probably explain what they actually do, is by telling you what they dont do.
std::move does not actually move anything.
std::forward does not actually forward anything.
You can see them as a cast.
std::move will always cast its argument to an rvalue.
std::forward will conditionally cast its value to an rvalue.
What I mean by conditionally cast is that if the argument is already of type
rvalue it will cast it to an rvalue, if its an lvalue it will cast it to an lvalue. (You will realize why this is usefull later)

Now suppose Foo is class that holds some resource, say _resource. This resource is big & very costly
to copy, lets say it is a std::vector of type T and contains N elements.
Code:
Foo v1;
Foo v2(v1); // Copy constructor is called (Foo(const Foo& other))
Foo v3(std::move(v1)); // Move constructor is called (Foo(Foo&& other))
// When v1 reaches the semicolon it will be destructed, but before its destructed
// the contents of v3 is swapped with v1, meaning v3 holds everything v1 held.
Now this doesn't mean you should always use move semantics, calling std::move on an lvalue
basically destructs everything the lvalue contained, because thats how rvalues work, they get destructed at the end
of the current statement for memory management (Created & Destroyed on the stack).

Now that I've showed you how std::move is used, I should probably show you how you
create your own move constructor, so it actually works.
Code:
// in class Foo
Foo(Foo&& other)
{
     // Now we just neatly swap the items in other with our own.
     std::swap(_resources, other._resources); 
}
That was simple right?

The next part may confuse you a little at first, but its extremely usefull as you do dont have to
re-create all your functions twice for an lvalue version & rvalue version. (std::forward is our saviour!):

Suppose we have a class that holds a std::vector of type T with the name of _vec.
Now in our class we have a function to add items to this vector, T may be a large user defined type, or similiar
making it cost performance to copy a std::vector of T, hence why we want to use perfect forwarding when possible.
Code:
template<typename _T>
void Add( _T&& element ) // when calling this function you should NOT do obj->Add<type> because 
// perfect forwarding relies on type deduction using universal references (typename of name T with && becomes a
// universal reference)
{
   // Now universal references can be lvalues or rvalues (see where I'm getting?)
   _vec.push_back( std::forward<_T>(element) );
}


I'll add more stuff to this when I feel like it, if you want anything specific changed/added to the thread you can add my skype and suggest stuff to be changed/added. This was supposed to in the C++ section but since I didn't actually finish it I'll wait untill its finished untill I post it there.





Resources: C++ Primer (5th), How to write Effective C++11 & Alot more resources by time (MSDN/cplusplus.com).
Loooks... Good. Never heard of C++11 but ty
Bumping this since it's very useful.
Posts 1–3 of 3 · Page 1 of 1

Post a Reply

Similar Threads

Tags for this Thread

None

Talk with us