现在的位置: 首页 > 综合 > 正文

C++ AMP: The Concurrency Runtime and Visual C++ 2010: Lambda Expressions

2013年12月08日 ⁄ 综合 ⁄ 共 5715字 ⁄ 字号 评论关闭

As a C++ programmer, you might have already started working with the Concurrency Runtime components in Visual Studio 2010 such as the Parallel Patterns Library (PPL) and the Asynchronous Agents Library. These libraries make it much easier to write parallel code that takes advantage of multi-core. I wanted to talk a bit about a few new features that are available in the Visual C++ 2010 compiler that make it even easier to write parallel code. This week I’ll start with lambda expressions, my favorite new language feature in Visual C++ 2010.

Lambda Expressions at a Glance

First we’ll take a look at a program that uses function objects, or functors, to determine which numbers in a random series are prime. Then I’ll explain a bit about lambda expressions and show you how to use them to simplify the program.

Consider the following program that determines which numbers in a random series are prime:

   1: // random-primes-functor.cpp
   2: // compile with: /EHsc
   3: #include <algorithm>
   4: #include <string>
   5: #include <random>
   6: #include <iostream>
   7:  
   8: // Determines whether the input value is prime.
   9: template<typename T>
  10: bool is_prime(T n)
  11: {
  12:    static_assert(std::is_integral<T>::value, "T must be of integral type.");
  13:  
  14:    if (n < 2)
  15:       return false;
  16:    for (T i = 2; i < n / i; ++i)
  17:    {
  18:       if ((n % i) == 0)
  19:          return false;
  20:    }
  21:    return true;
  22: }
  23:  
  24: // A functor class that inserts numbers that are prime at the back of the 
  25: // provided container type.
  26: // The template type Container must hold an integral type and also must 
  27: // support the push_back operation.
  28: template <typename Container>
  29: class InsertPrimes
  30: {
  31:    // Ensure that T is of integral type.
  32:    static_assert(std::is_integral<typename Container::value_type>::value, 
  33:       "Container must hold an integral type.");
  34:  
  35: public:
  36:    InsertPrimes(Container& primes)
  37:       : _primes(primes)
  38:    {
  39:    }
  40:  
  41:    // Inserts the number into the container if the number is prime.
  42:    void operator()(typename Container::value_type n) const
  43:    {
  44:       if (is_prime(n)) {                  
  45:          _primes.push_back(n);
  46:       }
  47:    }
  48:  
  49: private:   
  50:    Container& _primes; // The container to hold primes.
  51: };
  52:  
  53: // Prints values to the standard output stream in comma-separated form.
  54: // Type T must be supported by the wostream << operator.
  55: template <typename T>
  56: class PrintItems
  57: {
  58: public:
  59:    // Prints the provided element to the standard output stream.
  60:    // Elements are separated by a comma and a space character.
  61:    void operator()(const T& item)
  62:    {
  63:       std::wcout << _comma << item;
  64:       _comma = L", ";
  65:    }
  66: private:
  67:    std::wstring _comma; // Separates elements.
  68: };
  69:  
  70: int wmain()
  71: {
  72:    // Create a vector object that contains a few random integers.
  73:    std::vector<int> v(50);
  74:    std::generate(v.begin(), v.end(), std::mt19937(42));
  75:       
  76:    // Create a container that holds the elements of the array that 
  77:    // are prime.
  78:    std::vector<int> primes;
  79:  
  80:    // Insert the elements of the array that are prime at the back 
  81:    // of the container.
  82:    std::for_each(v.begin(), v.end(), InsertPrimes<std::vector<int>>(primes));
  83:  
  84:    // Print the results.
  85:    std::wcout << L"Found the following primes: " << std::endl;
  86:    std::for_each(primes.begin(), primes.end(), PrintItems<int>());
  87: }

 

This program produces the following output:

Found the following primes:

88409749, 911989541

Sidebar – In Visual C++ 2010, there is a more efficient way to populate the primes vector. Instead of using the for_each algorithm and a functor, you can use the std::copy_if algorithm:

std::copy_if(v.begin(), v.end(), std::back_inserter(primes), is_prime<int>);
However, I used for_each in this example so that I can show you how to convert code that uses a functor to use a lambda expression and so that we can more easily compare for_each to its parallel counterpart, parallel_for_each (more on parallel_for_each later). – End Sidebar

In this example program, the std::for_each algorithm applies a function object to each element in the specified range of elements of a container. This program uses the for_each algorithm two times. The first use of for_each(line 82) applies a function object of type InsertPrimes to insert the numbers into a std::vector object; the second use (line 86) applies a function object of type PrintItems to print the results to the console.

Although the use of function objects is a great way to maintain state across a number of function calls, they can have some disadvantages:

  • They are verbose. You must create a type definition, member variables to hold data, a constructor to initialize data, operator() to process data, and so on.
  • They can be tedious to write. The structure of most function object types fit the same pattern. They often require many lines of code, but the action that you want to express is often just a minor portion of that code.
  • They are prone to errors. The more function object types that you write by hand, the greater the chance is that you’ll make a minor typing error or inadvertently use a reference type instead of a non-reference type (or vice-versa). In the InsertPrimes class in this example, consider what happens if you declare the _primesmember variable as a reference type (Container&), but declare the parameter to the constructor as a non-reference type (Container). (Spoiler: you won’t see any primes in your results!)
  • They don’t always lend to reuse. The InsertPrimes class serves a very specific purpose, and doesn’t necessarily lend itself to other uses. Function objects can add clutter to your code that another programmer (or yourself) may have to mentally untangle down the road.

 

Visual C++ 2010 introduces lambda expressions. A basic way to think of a lambda expression is as an anonymous function that maintains state and that can access the variables that are available to the enclosing scope. More simply put, think of a lambda expression as a way to describe the work that you want to do and have the Visual C++ compiler write the function object type for you.

I won’t go into the specifics of the syntax of lambda expressions here. For more details, see Stephan’s postLambdas, auto, and static_assert: C++0x Features in VC10, Part 1 and Lambda Expressions in C++ and Lambda Expression Syntax on MSDN. With that said, let’s see how you might use lambda expressions to simplify our example program:

   1: // random-primes-lambda.cpp
   2: // compile with: /EHsc
   3: #include <algorithm>
   4: #include <string>
   5: #include <random>
   6: #include <iostream>
   7:  
   8: // Determines whether the input value is prime.
   9: template<typename T>
  10: bool is_prime(T n)
  11: {
  12:    static_assert(std::is_integral<T>::value, "T must be of integral type.");
【上篇】
【下篇】

抱歉!评论已关闭.