次の方法で共有


From the MVPs: Introduction to C++ 11 in Visual Studio 2013

This is the 34th in our series of guest posts by Microsoft Most Valued Professionals (MVPs). You can click the “MVPs” tag in the right column of our blog to see all the articles.

Since the early 1990s, Microsoft has recognized technology champions around the world with the MVP Award. MVPs freely share their knowledge, real-world experience, and impartial and objective feedback to help people enhance the way they use technology. Of the millions of individuals who participate in technology communities, around 4,000 are recognized as Microsoft MVPs. You can read more original MVP-authored content on the Microsoft MVP Award Program Blog.

This post is by Visual C++ MVP Alon Fliess. Thanks, Alon!

Introduction to C++ 11 in Visual Studio 2013

I just had a conversation with one of my colleagues. He told me “I have started looking at C++". "I didn’t realize that it is such a productive language”, he added. You see, my colleague is a gifted C# developer, and he knew that C++ is an "old" language that one uses to program Operating Systems, Drivers, High Performance algorithms, communicate with hardware devices and make your life interesting but also complicated. My friend was born the year that Bjarne Stroustrup invented C with Classes, the first name that he gave to C++, at AT&T Bell laboratories.

For a C# developer, C++, in many cases, is the legacy code that you need to interop with. For me and many other veteran developers, C++ is one of the sharpest tools in our toolbox. As a Windows developer, I tend to choose the right tool for the job, be it C++ native code, C# with .NET or even JavaScript.

Modern C++ is a very strong programming language. You can use it to develop almost everything, from low level OS code to mobile applications. You can develop cross-platform code and libraries without sacrificing the quality of your software and the performance of your application.

If you used to develop with C++ and moved to other programming languages, it is time to look again at one of the most evolving languages today: C++.

C++ is a multi-paradigm language. It supports structured programming, Object Based and Object Oriented programming, generic meta-programming and functional programming. The new version, C++ 11, has improved all of the above programming styles without sacrificing the important C++ pillars such as targeting real life problems, performance & backward compatibility. The new language features even result in higher performance applications and providing a much better development productivity.

The language evolution itself is not enough. For a fast and easy development process, we need great tools. The Visual C++ team at Microsoft worked hard to provide the best tooling for us: Visual C++ in Visual Studio 2013.

In this article, I will introduce the major improvements of C++ 11 in Visual Studio 2013. I cannot get into all the tiny details about the features that I will present and I cannot tell you about all of the new features, but I hope that once you will read the article you will want to read and learn more.

C++ 11 Language Changes

C++ 11 is the new standard of C++. The standard is full with goodies and it takes time for compiler vendors to implement all of it in a short time period. Visual Studio 2013 is the third release of Visual Studio that supports the standard, with each release of Visual Studio adding more C++ 11 features. The Microsoft Visual C++ team is committed to provide full coverage of the standard, but it will take time to fulfil all of the features.

In Visual Studio 2010, we have got some of the more important core C++ 11 features, among them are R-Value references support, auto & decltype, nullptr and Lambda functions.

Before we dive into the new C++ 11 R-Value reference, here is a short reminder of passing arguments to a function in C++.

C++ has different ways to pass arguments to and from a function. You can pass a reference by value, by (L-Value) reference or by pointer. Let us use a simple Person class:

classPerson

{

private:

       string_firstName;

       string_lastName;

       int_age;

public:

       Person(conststring &firstName, conststring &lastName, intage)

              : _firstName(firstName), _lastName(lastName), _age(age)

       {}

 

       voidPrintDetails(ostream &out) const

       {

              out << _firstName << ' ' << _lastName << " is " << _age << endl;

       }

};

The Person constructor takes the string arguments as an L-Value const reference. This is the common practice; the input parameters are copied to the _firstname and _lastname data members so making them constant is reasonable and allows passing variable as well as constant values. Passing an L-Value reference is like passing the address of the argument without the complication of pointers. The age argument is an int, and it is fast and easy to pass it by value.

voidPrintPersons(constPersonp1, constPerson &p2, constPerson *p3)

{

       p1.PrintDetails(cout);

       p2.PrintDetails(cout);

       p3->PrintDetails(cout);

}

In this function, we see that p1 and p2 provide the same dot syntax when referring the object instance method, however only p2 and p3 provide the same performance. In the case of p1, a new object instance is created and the data of Person is copied, while in p2 and p3 we use the original instance.

Person(string &&firstName, string &&lastName, intage)

       : _firstName(move(firstName)),
_lastName(move(lastName)),
_age(age)
{}

Adding the above constructor overloaded version, allows passing the string values by R-Value references. The standard library implementation of the string class uses an array of characters to hold the text. The new constructor is called when we pass a temporary expression – an object that its lifetime ends after the constructor returns. This allows the constructor to extract the inner text array from the supplied string firstName and move it to the member class _firstName (and from lastName to _lastName.) hence R-Values allow passing and using temporary objects (strings in our case) without the need to copy them. We can extract the inner data field from the R-Value instance since it is temporary and subject to be deleted anyway.

Another example:

classPhoneBook

{

public:

       voidAddPerson(constPerson &p)

       {

              _pepole.push_back(p);

       }

 

       voidAddPerson(Person &&p)

       {

              _pepole.push_back(move(p));

       }

 

private:

       vector<Person> _pepole;

};

int_tmain(intargc, _TCHAR* argv[])

{

       Personp("Alon", "Fliess", 44);

      

       PhoneBookphoneBook;

 

       phoneBook.AddPerson(p);

       phoneBook.AddPerson(Person("Alon", "Fliess", 44));

 

       return 0;

}

The first call to AddPerson creates a copy of person p while the second call just pushes the content of the temporary Person instance into the vector. R-Value references have a huge impact on performance. It provides an easy way of using value semantics without the overhead of copying too many instances. R-Value creates moveable objects as well as provides perfect forwarding. These subjects are beyond the scope of this article, but go ahead and read my post on them here.

The auto keyword is a syntax sugar that makes type declaration easy and provides a type-safe type declaration abstraction. Lambda functions are a major productivity boost. You do not need to build functors (operator () ) or use any of the STL bind methods to use an STL algorithm in a simple form:

constPerson &FindFirstPerson(conststring &lastName)

{

       autoresult = find_if(cbegin(_pepole), cend(_pepole),

              [&](constPerson &p) { returnp.GetLastName() == lastName; });

             

       if (result != cend(_pepole))

              return *result;

 

       throwexception("Can't find person");

}

 

Here the auto gets the type of the const iterator of vector<person> . You do not need to type it, and if you change the container, you do not need to change the result type. The lambda expression makes it so easy to write a predicate. Read here about C++ lambdas.

In Visual Studio 2012 we have got fixes for R-Value references and Lambdas, Full implementation of strongly typed enum and forward declaration of enums, the new range-based loop, library support for threading, atomic operations and chrono, in addition to a few extra additions such as the override and final keywords.

For example, a range based loop would appear like this:

voidPrintPhoneBook(ostream &out) const

{

       for (constPerson &p : _pepole)

       {

              p.PrintDetails(out);

       }

}

 

In Visual Studio 2013, we have many new features; the most important ones are non-static data member initializers, initializer lists, delegate constructors, defaulted and deleted functions, raw string literals, and variadic templates.

Beside variadic templates, all of the other features are mainly productivity features. We could handle the language quite well without them, but we had to use tricks and patterns and we had to write more code to achieve the same results. Let us see an example of these features. We will start with our PhoneBook and Person classes:

#include<vector>

#include<algorithm>

#include<exception>

#include<initializer_list>

 

usingnamespacestd;

 

classPerson

{

private:

       string_firstName = "Empty";

       string_lastName = "Name";

       int_age = 0;

 

public:

       Person() = default;

 

       Person(conststring &firstName, conststring &lastName, intage)

              : _firstName(firstName), _lastName(lastName), _age(age)

       {}

 

       Person(string &&firstName, string &&lastName, intage)

              : _firstName(move(firstName)), _lastName(move(lastName)),
_age(age) {}

 

       Person(string &firstName, intage) : Person(firstName, "", age) {}

 

       Person(string &&firstName, intage) : Person(
forward<string &&>(firstName), "", age) {}

      

 

       voidPrintDetails(ostream &out) const

       {

              out << _firstName << ' ' << _lastName << " is " << _age << endl;

       }

 

};

 

classPhoneBook

{

public:

       PhoneBook() = default;

       PhoneBook(initializer_list<Person> list) : _pepole(list) {}

 

       voidPrintPhoneBook(ostream &out) const

       {

              for (constPerson &p : _pepole)

              {

                     p.PrintDetails(out);

              }

              out << endl;

       }

 

private:

       vector<Person> _pepole;

};

 

int_tmain(intargc, _TCHAR* argv[])

{

Personbill("Bill", "Clinton", 67);

       PhoneBookanotherBook({ Person(), bill,
Person("Ronald", "Reagan", 102),
Person("Jimmy", 89) });

       anotherBook.PrintPhoneBook(cout);

}

 

The result is:

Empty Name is 0

Bill Clinton is 67

Ronald Reagan is 102

Jimmy is 89

 

The class Person has a non-static data member initialization, so the result is that the first person is: “Empty Name is 0” . Since we create a non-default constructor of Person, we no longer have the default compiler created constructor. We can get it back using the defaulted constructor syntax: Person() = default;

In addition to the previous constructors, we now have another two constructors that take only the first name and the age and delegate the call to other constructors. The forward<string &&> helps keeping the temporary string argument a temporary hence it calls the R-Value constructor. Our PhoneBook class also uses the default form, because now it has another constructor that accepts an initializer_list that causes the removal of the default compiler generated constructor. Initializer_list is a new mechanism that enables the following collection initialization form:

Constructor:

PhoneBook(initializer_list<Person> list) : _pepole(list) {}

 

Initialization:

PhoneBookanotherBook({
Person(), bill,
Person("Ronald", "Reagan", 102),
Person("Jimmy", 89)

});

 

Person() uses the default constructor with the non-static data member initializers, bill is a simple L-Value reference, Ronald is an R-Value that uses the R-Value reference constructor and Jimmy uses the R-Value delegate constructor.

Now let us see another example:

#include"stdafx.h"

#include<array>

#include<memory>

#include<assert.h>

 

usingnamespacestd;

 

structPixel

{

       unsignedcharAlpha = 0;

       unsignedcharRed = 0;

       unsignedcharGreen = 0;

       unsignedcharBlue = 0;

 

       Pixel() = default;

       Pixel(unsignedcharalpha,

              unsignedcharred,

              unsignedchargreen,

              unsignedcharblue) :

                     Alpha(alpha), Red(red),

                     Green(green), Blue(blue)

       {

       }

};

 

typedefarray<array<Pixel, 1024>, 1024> surface_t;

 

classMovableNotCopyable

{

private:

       unique_ptr<surface_t> _screen;

 

public:

       MovableNotCopyable() = default;

       MovableNotCopyable(Pixelpixel)

       {

              _screen = unique_ptr<surface_t>(newsurface_t());

 

              for (array<Pixel, 1024> &pixelArray : *_screen)

              {

                     for (Pixel &p : pixelArray)

                     {

                           p = pixel;

                     }

              }

       }

 

       MovableNotCopyable(constMovableNotCopyable &o) = delete;

       MovableNotCopyable &operator=(constMovableNotCopyable &o) = delete;

 

       MovableNotCopyable(MovableNotCopyable &&o) : _screen(move(o._screen)) {}

 

       MovableNotCopyable &operator=(MovableNotCopyable &&o)

       {

              assert(this != &o);

 

              _screen = move(o._screen);

 

              return *this;

       }

};

 

 

int_tmain(intargc, _TCHAR* argv[])

{

       MovableNotCopyablescreen1;

       MovableNotCopyablescreen2(Pixel{ 100, 100, 100, 255 });

 

// screen1 = screen2; //Compilation Error

       screen1 = move(screen2);

       return 0;

}

 

In this sample, we create a movable non-copy-able type. We use the delete form to remove the default copy constructor and assignment operator:

MovableNotCopyable(constMovableNotCopyable &o) = delete;

MovableNotCopyable &operator=(constMovableNotCopyable &o) = delete;

 

We define move constructor and move assignment operator:

MovableNotCopyable(MovableNotCopyable &&o) : _screen(move(o._screen)) {}

 

MovableNotCopyable &operator=(MovableNotCopyable &&o)

{

       assert(this != &o);

       _screen = move(o._screen);

       return *this;

}

 

This ensures that the huge rectangular array will not be copied, but we can still move it to and from methods and local variables without paying the copy penalty. Another new C++ 11 feature that we can spot in this sample is the uniform initialization form:

MovableNotCopyablescreen2(Pixel{ 100, 100, 100, 255 });

 

The last C++ 11 new feature of Visual Studio 2013 in this article is variadic templates. Variadic templates are a template types that take variable number of arguments, something similar to the stdarg (printf, …) but for templates. It saves the complicated usage of template type overloading and macros that you can find in C++ libraries implementations before C++ 11 for types such as tuple. It also provides a mechanism for static type check during compilation when combining variadic templates with template specialization, type traits and static assert. Variadic templates deserves their own separate article, but here is a taste of it:

// VariadicTemplate.cpp : Defines the entry point for the console application.

//

 

#include"stdafx.h"

#include<iostream>

#include<array>

#include<numeric>

 

usingnamespacestd;

 

 

classBase1

{

};

 

classBase2

{

};

 

template <class ...D>

classDerived : virtualpublicD...

{

       static_assert(sizeof...(D) >= 2,
"There must be at least two base classes");

};

 

template <int ...I>

intSum()

{

       array<int, sizeof...(I)> numbers = { I... };

       intresult = accumulate(begin(numbers), end(numbers), 0);

       returnresult;

}

 

int_tmain(intargc, _TCHAR* argv [])

{

       //Derived<Base1> d1; //C2338: There must be at least two base classes

       Derived<Base1, Base2> d2;

 

       cout << Sum<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>() << endl;

 

       return 0;

}

 

The first example is a compile-time check that makes sure that the Derived<> template gets instantiated with at least two types.

The second example shows how we can use integer constants with variadic template. We are going to look at some assembly code that the VC++ compiler generated, so don’t panic!

 

00007FF68EB5254F call Sum<1,2,3,4,5,6,7,8,9,10> (7FF68EB5113Bh)

template <int ...I>

int Sum()

{

…prolog

       array<int, sizeof...(I)> numbers = { I... };

00007FF68EB52626 mov dword ptr [rsp+28h],1

00007FF68EB5262E mov dword ptr [rsp+2Ch],2

00007FF68EB52636 mov dword ptr [rsp+30h],3

00007FF68EB5263E mov dword ptr [rsp+34h],4

00007FF68EB52646 mov dword ptr [rsp+38h],5

00007FF68EB5264E mov dword ptr [rsp+3Ch],6

00007FF68EB52656 mov dword ptr [rsp+40h],7

00007FF68EB5265E mov dword ptr [rsp+44h],8

00007FF68EB52666 mov dword ptr [rsp+48h],9

00007FF68EB5266E mov dword ptr [rsp+4Ch],0Ah

       int result = accumulate(begin(numbers), end(numbers), 0);

      std::accumulate<std::_Array_iterator<int,10>,int> (7FF68EB5105Ah)

00007FF68EB526EF mov dword ptr [rsp+54h],eax

       return result;

00007FF68EB526F3 mov eax,dword ptr [rsp+54h]

}

 

As you can see the compiler takes the variadic template and spans all the arguments to be inline and the assembly code shows that the template function uses the integer constants 1 to 10 (0Ah).

Summary

C++ is fast evolving, the next standard will be released next year as C++ 14 and the committee is busy these days to define the C++ 17 features and standards. C++ is a modern language that provides everything you need to develop anything from Windows 8.1 or Windows Phone 8 applications to high performance cross platform server side and cloud based systems.

The new C++ is much easier to develop, you do not need to deal with memory handling, and you can use shared or unique pointers and even better use value semantics with R-Value references without sacrificing performance.

If you used to be a C++ developer and moved to other programming languages, or you are a C# developer and want to enrich your toolbox, this is the time to learn an old new language!

Comments

  • Anonymous
    December 09, 2013
    classPerson voidPrintPersons(constPersonp1, constPerson &p2, constPerson *p3)

  • Anonymous
    December 10, 2013
    Sorry - that could have been my fault when I took this post from its original submission into Live Writer, which I use to post to the blog. - Kim

  • Anonymous
    January 06, 2014
    Please redo the code. There are no spaces, it's hard to read it. If it wasn't for the colors it would be impossible.

  • Anonymous
    January 08, 2014
    Perhaps the code can be attached as file as well?