Planet ccppbrasil.org

April 21, 2010

Fábio Galuppo

Concurrency Double Coverage

 
This post is equivalent to 2 of them.
 
Here I'm pointing the materials for 2 talks I'd given about Concurrency:
 
Paralelismo com .NET Framework 4.0 - MSDN Brazil WebCast
Both talks covered concurrency/parallel programming, of course. Wink
 
The first one was about .NET Framework 4.0 and the new features including: Task Parallel Library, PLINQ, Concurrency Collections, and a lot of managed code and concurrency stuff. The samples was written in C#. During the Q&A section someone asked about the use of those features with UI. I put an extra sample covering this issue!
 
The second one was about C++, my number one favorite programming language, and concurrency. In fact, was about a Introduction to ConcRT, a concurrency runtime shipped with Visual C++ 10.  
 
 
If you want to try these samples and amazing features for free, you can download the Visual C++ 10 Express and the other Visual Studio 10 tools.
 
Every post has a code. Thus, it isn't different:
 
Find Files (sequential and parallel) in C#:
 
using
System;
using
System.Collections.Generic;
using
System.Threading.Tasks;
using
System.Collections.Concurrent;

namespace
DotNet4_ParallelSamples
{
using dir = System.IO.Directory
;

abstract class
FileFinder
{
    public IEnumerable<string> FindFiles(string dir_path, string
filenameHint)
    {
        Action<string
> addItem;
        var filesFound = CreateResultBuffer(out
addItem);
        FindFilesCore(dir_path, filenameHint, addItem);
        return
filesFound;
    }

    protected string[] GetFiles(string
dir_path)
    {
        var files = new string[0
];
        try

        {
            files =
dir.GetFiles(dir_path);
        }
        /* ignore */
        catch (UnauthorizedAccessException
) { }
        catch (System.IO.IOException
) { }
        /* ignore */

        return files;
    }

    protected string[] GetDirectories(string
dir_path)
    {
        var directories = new string[0
];
        try
        {
            directories =
dir
.GetDirectories(dir_path);
        }
        /* ignore */

        catch (UnauthorizedAccessException) { }
        catch (System.IO.IOException
) { }
        /* ignore */

        return directories;
    }

    protected abstract IEnumerable<string> CreateResultBuffer(out Action<string
> addFunc);

    protected abstract void FindFilesCore(string dir_path, string filenameHint, Action<string
> addToResult);
}

class SequentialFileFinder : FileFinder

{
    protected override IEnumerable<string> CreateResultBuffer(out Action<string> addFunc)
    {
        var buffer = new List<string
>();
        addFunc = buffer.Add;

        return
buffer;
    }

    protected override void FindFilesCore(string dir_path, string filenameHint, Action<string
> addToResult)
    {
        foreach (var file in
GetFiles(dir_path))
        {
            if
(file.Contains(filenameHint))
            {
                addToResult(file);
            }
        }

        foreach (var directory in
GetDirectories(dir_path))
        {
            FindFilesCore(directory, filenameHint, addToResult);
        }
    }
}

class ParallelFileFinder : FileFinder

{
    protected override void FindFilesCore(string dir_path, string filenameHint, Action<string> addToResult)
    {

        Parallel.ForEach(GetFiles(dir_path), (file) =>
        {
            if
(file.Contains(filenameHint))
            {
                addToResult(file);
            }
        });

        Parallel.ForEach(GetDirectories(dir_path), (directory) =>
        {
            FindFilesCore(directory, filenameHint, addToResult);
        });
    }

    protected override IEnumerable<string> CreateResultBuffer(out Action<string
> addFunc)
    {
        var buffer = new ConcurrentBag<string
>();
        addFunc = buffer.Add;

        return
buffer;
    }
}

}
 
Find Files (sequential and parallel) in C++:
 
#include
<windows.h>
#include
<ppl.h>

using
namespace Concurrency;

#include
<list>
#include
<string>
#include
<algorithm>
#include
<iostream>

using
namespace std;

#include
"_helper.hpp"

struct
Directory
{
    static void GetDirectories( __in const wchar_t
* path, __out list<wstring>& directories )
    {
        EnumIOItems( path, [&]( __in
const
WIN32_FIND_DATA& ffd )
        {
            if ( ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && L'.' != ffd.cFileName[0
] )
                directories.push_back( wstring( path ).append( ffd.cFileName ).append( L
"\\"
) );
        });
    }

    static void GetFiles( __in const wchar_t
* path, __out list<wstring>& files )
    {
        EnumIOItems( path, [&]( __in
const
WIN32_FIND_DATA& ffd )
        {
            if
( !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
                files.push_back( wstring( path ).append( ffd.cFileName ) );
        });
    }

private
:
    template<class
Function>
    static void EnumIOItems( __in const wchar_t
* path, __in Function _Func )
    {
        WIN32_FIND_DATA ffd = { NULL };
        HANDLE hFind = FindFirstFile( wstring( path ).append(L
"*"
).c_str(), &ffd );
        if
( INVALID_HANDLE_VALUE != hFind )
        {
            do _Func( ffd ); while( 0
!= FindNextFile( hFind, &ffd ) );
            FindClose( hFind );
        }
    }
};

class
FileFinder
{

public
:
    typedef
list<wstring> result_type;
    typedef
result_type& result_type_ref;

    FileFinder(){}
    virtual
~FileFinder(){}

    void FindFiles( __in const wchar_t* path, __in const wchar_t
* filenameHint, __out result_type_ref filesFound )
    {
        FindFilesCore( path, filenameHint, filesFound );
    }

protected
:
    void GetFiles( __in const wchar_t* path, __out result_type_ref files )
const
    {
        Directory::GetFiles( path, files );
    }

    void GetDirectories( __in const wchar_t* path, __out result_type_ref directories ) const
    {
        Directory::GetDirectories( path, directories );
    }

    template<class Container, class Item> void add( Container& c, const Item& i ) const { c.push_back( i ); }

    virtual void FindFilesCore( __in const wchar_t* path, __in const wchar_t* filenameHint, __out result_type_ref filesFound ) const = 0
;
};

class
SequentialFileFinder : public FileFinder
{
protected
:
    virtual void FindFilesCore( __in const wchar_t* path, __in const wchar_t* filenameHint, __out result_type_ref filesFound )
const
    {
        list<wstring> files;
        GetFiles( path, files );
        for_each( files.begin(), files.end(), [&]( __in
const wstring& file )
        {
            if
( wstring::npos != file.find( filenameHint ) )
                add( filesFound, file );
        } );

        list<wstring> directories;
        GetDirectories( path, directories );
        for_each( directories.begin(), directories.end(), [&]( __in
const
wstring& dir )
        {
            FindFilesCore( dir.c_str(), filenameHint, filesFound );
        } );
    }
};

class
ParallelFileFinder : public FileFinder
{

protected
:
    virtual void FindFilesCore( __in const wchar_t* path, __in const wchar_t* filenameHint, __out result_type_ref filesFound )
const
    {
        combinable<result_type> filesFound_partial;

        list<wstring> files;
        GetFiles( path, files );
        parallel_for_each( files.begin(), files.end(), [&]( __in
const wstring& file )
        {
            if
( wstring::npos != file.find( filenameHint ) )
                add( filesFound_partial.local(), file );
//<-
        } );

        join( filesFound_partial, filesFound );
//<-

        list<wstring> directories;
        GetDirectories( path, directories );

        parallel_for_each( directories.begin(), directories.end(), [&]( __in
const wstring& dir )
        {
            FindFilesCore( dir.c_str(), filenameHint, filesFound );
        } ); 
    }

private
:
    void join( __in combinable<result_type>& filesFound_partial, __out result_type_ref filesFound )
const
    {
        filesFound_partial.combine_each( [&](result_type_ref local)
        {
            copy( local.begin(), local.end(), back_insert_iterator<result_type>( filesFound ) );
            local.clear();
        } );
    }
};

April 21, 2010 10:13 PM

April 20, 2010

Wanderley Caloni

Typedef arcaico

A API do Windows geralmente prima pela excelência em maus exemplos. A Notação Húngara e o Typedef Arcaico são duas técnicas que, por motivos históricos, são usados a torto e a direito pelos códigos de exemplo.

Já foi escrito muita coisa sobre os prós e contras da notação húngara. Já o typedef arcaico, esse pedacinho imprestável de código, ficou esquecido, e hoje em dia traz mais dúvidas na cabeça dos principiantes em C++ do que deveria. Para tentar desobscurecer os mitos e fatos, vamos tentar explicar o que significa essa construção tão atípica, mas comum no dia-a-dia.

Vejamos um exemplo típico desse pequeno Frankenstein semântico:

typedef struct _MINHASTRUCT {
   int x;
   int y;
}
MINHASTRUCT, *LPMINHASTRUCT;

Bom, eu nem sei por onde começar. Talvez pelo conceito de typedef.

Typedefs

Um typedef, basicamente, é um apelido. Você informa um tipo e define "outro tipo".

typedef <tipo> apelido;

O <tipo> é tudo que fica entre o typedef e o novo nome, que deve ser um identificador válido na linguagem. Por exemplo, a empresa onde trabalho fez um typedef informal do meu nome:

typedef Wanderley Caloni Wandeco;

Se, futuramente, eu sair da empresa e entrar outro "Wanderley alguma-coisa", será possível usar o apelido novamente, bastando alterar o typedef:

typedef Wanderley Cardoso Wandeco;

Bom, "outro tipo" é forma de dizer. Isso é uma descrição errônea em muitos livros. De fato, o compilador enxerga o mesmo tipo com outro nome, daí chamarmos o typedef de apelido, mesmo.

/** @file dois_apelidos.cpp */
#include <iostream>
 
using namespace std;
 
struct Struct
{
   int x;
   int y;
};
 
typedef Struct Struct1;
typedef Struct Struct2;
 
int main()
{
   Struct1 s1;
   Struct2 s2;
 
   cout << typeid(s1).name() << endl;
   cout << typeid(s2).name() << endl;
}

C:\Tests>cl /EHsc dois_apelidos.cpp
...
/out:dois_apelidos.exe
dois_apelidos.obj

C:\Tests>dois_apelidos.exe
struct Struct
struct Struct

Granularidade dos tipos

Tipos simples são fáceis de entender porque possuem seus símbolos no mesmo lugar:

int x;
char c;
long p;

Já os tipos um pouco mais complicados permite alguma mudança aqui e acolá:

int* x;
char *y;
long * p;

Essa liberdade da linguagem, mesmo sendo um recurso útil, pode ser bem nocivo dependendo de quem olha o código:

int x, y; // dois inteiros
int * x, y; // um ponteiro para inteiro e um inteiro
int x, *y; // um inteiro e um ponteiro para inteiro
int *x, y; // um ponteiro para inteiro e um inteiro

Em algumas formas da sintaxe, além de ser inevitável, gera bastante desconfiança:

// Um ponteiro para função que recebe dois inteiros e não retorna nada.
typedef void (*FP)(int, int);

// Um ponteiro para função que recebe dois inteiros e não retorna nada.
void (*)(int, int);

// Um cast para ponteiro para função que recebe dois inteiros e não retorna nada.
( ( void (*)(int, int) ) pf )(x, y);

#include <iostream>
 
void func(int x, int y)
{
   std::cout << x << '-' << y << '\n';
}
 
int main()
{
   void* pf = func;
   ( ( void (*)(int, int) ) pf )(3, 14);
}

Structs em C++

Antigamente, as structs eram construções em C que definiam um agregado de tipos primitivos (ou outras structs) e que poderiam gerar variáveis desse tipo em qualquer lugar, desde que informado seu nome e que se tratasse de uma struct:

/** @file structs.cpp */
struct MyStruct { int x, y; };
 
void func1()
{
   struct MyStruct ms;
   //...
}
 
void func2(struct MyStruct msa)
{
   //...
}
 
int main()
{
   struct MyStruct ms;
   func2(ms);
}

Para evitar toda essa digitação, os programadores usavam um pequeno truque criando um apelido para a estrutura, e usavam o apelido no lugar da struct (apesar de ambas representarem a mesma coisa).

struct MyStruct { int x, y; };
typedef struct MyStruct MS;

ou

typedef struct MyStruct { int x, y; } MS;
struct MyStruct ms1; // ainda prolixo
MS ms2; // mais simples

Com a definição da linguagem C++ padrão, e mais moderna, essa antiguidade foi removida, apesar de ainda suportada. Era possível usar apenas o nome do struct como seu tipo:

/** @file structs.cpp */
struct MyStruct { int x, y; };
 
void func1()
{
   /*struct*/ MyStruct ms;
   //...
}
 
void func2(/*struct*/ MyStruct msa)
{
   //...
}
 
int main()
{
   /*struct*/ MyStruct ms;
   func2(ms);
}

Porém, isso vai um pouco além de quando a Microsoft começou a fazer código para seu sistema operacional. Naquela época, o padrão ainda estava se formando e existia mais ou menos um consenso de como seria a linguagem C++ (sem muitas alterações do que de fato a linguagem C já era). De qualquer forma, a linguagem C imperava bem mais que C++. Dessa forma, já era bem formada a ideia de como declarar uma struct: a forma antiga.

typedef struct _MINHASTRUCT {
   int x;
   int y;
}
MINHASTRUCT, *LPMINHASTRUCT;

Além do uso controverso do _sublinhado para nomear entidades (que no padrão foi recomendado que se reservasse aos nomes internos da biblioteca-padrão) e do uso de MAÍUSCULAS_NO_NOME (historicamente atribuído a nomes definidos no pré-processador), o uso do typedef atracado a um struct era muito difundido. E ficou ainda mais depois que a API do Windows foi publicada com essas definições.

Como fazer,então?

Ora, do mesmo jeito que é feito há vinte anos: sem typedefs. O próprio paradigma da linguagem, independente de padrões de APIs, de sistemas operacionais ou de projetos específicos já orienta o programador para entender o que o espera na leitura de um código-fonte qualquer. Qualquer pessoa que aprendeu o básico do básico sobre ponteiros e structs consegue ler o código abaixo:

// Papai, o que que é isso?
// Ora, filho, apenas uma definição de estrutura!
//
struct MinhaStruct {
   int x;
   int y;
};
 
// muitas linhas abaixo...
 
void func(MinhaStruct* ms)
{
   // asterisco significa ponteiro para MinhaStruct!
}
 
int main()
{
   MinhaStruct ms;
   func(&ms);
}

Agora, para entender a forma antiga, ou você se baseou no copy&paste dos modelos Microsoftianos, ou seja, decoreba, ou você é PhD em Linguagem C/C++ e padrões históricos de linguagens legadas. Se não é, deveria começar o curso agora.

// Papai, o que que é isso?
// Ora, filho, apenas uma definição de sinônimo da struct
// _MINHASTRUCT, cujo nome não é usado, para dois nomes
// em maiúsculas, apesar se não serem defines, com uma
// nomenclatura de ponteiro que eu nunca vi na vida (obs: 
// papai programa em um sistema não-Windows).
//
typedef struct _MINHASTRUCT {
   int x;
   int y;
}
MINHASTRUCT, *LPMINHASTRUCT;
 
// muitas linhas abaixo...
 
void func(LPMINHASTRUCT ms)
{
   // o que diabos é um LP, mesmo?
}
 
int main()
{
   MINHASTRUCT ms;
   func(&ms);
}

Código Antes x Depois no Visual StudioDa mesma forma, o uso de uma estrutura simples de tipos mantém a lista de nomes do seu projeto limpa e clara. Compare o visualizador de classes em projetos Windows com algo mais C++ para ter uma ideia.

É claro, essa é apenas uma sugestão. Existem vantagens em sua utilização. Existe alguma vantagem no modo antigo? Existe: a Microsoft usa, e talvez mais pessoas usem. Basta a você decidir qual deve ser o melhor caminho.

Atualização

De acordo com o leitor  Adriano dos Santos Fernandes, a obrigatoriedade do nome struct após seu nome continua valendo para a linguagem C padrão, assim como no compilador GCC ocorre um erro ao tentar omiti-la. Apenas na linguagem C++ essa obrigatoriedade não existe mais.

Eu não fiz meus testes, mas confio no diagnóstico de nosso amigo. A maior falha do artigo, no entanto, é usar a linguagem C como base, quando na verdade ele deveria falar sobre o uso desses typedefs em C++. Esse erro também foi corrigido no original.

by Wanderley Caloni at April 20, 2010 12:26 PM