Baixe o app para aproveitar ainda mais
Prévia do material em texto
Apostila C++ 11 Declarações auto Quando se declara uma variável do tipo auto o compilador irá deduzir o tipo da variável através da expressão de inicialização, ou seja, que toda variável de tipo auto deve ser inicializada com algum valor, caso contrario teremos erro de compilação. #include <iostream> int main() { auto a = 1; auto b = "AGIT"; const auto c = 2; const auto d = "INFORMATICA"; vector<string> v1; vector<string>::iterator it = v1.begin(); auto it2 = v1.begin(); //auto e; Erro auto não inicializada //const auto f; Erro auto não inicializada std::cout << a << '\n' << b << '\n' << c << '\n' << d << '\n'; return 0; } Laço for baseado em intervalos O Laço for baseado em intervalos é uma nova sintaxe de C++ que nos permite percorrer o conteúdo de qualquer container que suporte o conceito de intervalos. Podemos utiliza-lo em qualquer classe que possa ser acessada através das funções Begin e end, classes containers da biblioteca padrão e arrays. #include <iostream> #include <list> using namespace std; int main() { list<int> li; li.push_back(1); li.push_back(2); li.push_back(3); li.push_back(4); li.push_back(5); /* Sintaxe anterior para percorrer containners for(list<int>::iterator it = li.begin(); it != li.end(); ++it) cout << *it << '\n'; */ //Sintaxe C++ 11 for(auto it : li) cout << it << '\n'; /* Ou com referencia for(auto &it : li) cout << it << '\n'; */ return 0; } Nullptr Usado para indicar que um ponteiro é nulo, nullptr pode ser convertido para qualquer tipo de ponteiro e também para boleano, mas não pode ser usado em operações que usam tipos integrais. No padrão anterior utilizávamos 0 para indicar que um ponteiro é nulo, mas se por exemplo: houvessem duas funções com mesmo nome void teste(int a) e void teste(int *a) e quiséssemos chamar void teste(int *a), mas sem passar um endereço de outra variável teríamos de chamar teste(0), porem com isso o compilador iria se confundir com a outra função teste que recebe um numero inteiro e iria chamar a função que não era para ser chamada. Com o novo padrão podemos chamar assim: teste(nullptr), isso irá garantir que a função que recebe um ponteiro como parâmetro será chamada. #include <iostream> using namespace std; void teste(int a) { cout << "void teste(int a)\n"; } void teste(int *a) { cout << "void teste(int *a)\n"; } int main() { teste(0);//Se confudi com inteiro ao inves de chamar o int *a teste(nullptr);//Garante que void teste(int *a) será chamada return 0; } Enumerações avançadas Nas enumerações tradicionais o tipo utilizado para armazenar uma enumeração é dependente da implementação o que pode nos trazer um gasto desnecessário de memória, como por exemplo: vamos criar uma enumeração com apenas dois enumeradores um vale 1 e outro vale 2, o tamanho de 1 byte seria mais que o suficiente para armazena-los, mas não sabemos se naquele ambiente será usado 2, 4, 8, 10 bytes para a enumeração, com isso podemos ter um gasto desnecessário de memória. As enumerações avançadas nos permite definir qual definir o tamanho da enumeração. #include <iostream> using namespace std; enum Frutas { BANANA, PERA }; enum Frutas_ENUM_AVANCADA : char//Dizemos que a enum deverá ocupar o tamanho de um char { BANANA_ENUM_AVANCADA, PERA_ENUM_AVANCADA }; int main() { Frutas FR; //Quantos bytes ocupa fruta? cout << sizeof(FR) << '\n'; //No caso dessa maquina 4 bytes Frutas_ENUM_AVANCADA FRA; cout << sizeof(FRA) << '\n'; return 0; } Enumerações com escopo As enumerações tradicionais possuíam uma serie de problemas como por exemplo: enumeradores com mesmo escopo da enumeração, são conversíveis para números inteiros, e o tipo utilizado para armazenar o enumerador é definido pelo compilador. #include <iostream> using namespace std; /* enum Frutas { BANANA, PERA }; enum Semana { SEGUNDA_FEIRA, TERCA_FEIRA, QUARTA_FEIRA, QUINTA_FEIRA, SEXTA_FEIRA, SABADO, DOMINGO }; //PRIMEIRO PROBLEMA //Em enumerações tradicionais não podemos ter enumeradores com mesmo nome, mesmo que estejam contidos //em enumerações diferentes, pois caso houvesse teriamos conflito de nomes e erro de compilação. enum Frutas_2 { BANANA, PERA }; */ //Enumeração com escopo enum class Frutas_Com_Escopo : char { BANANA, PERA }; enum class Frutas_Com_Escopo_2 : char { BANANA, PERA }; enum class Semana_Com_Escopo : char { SEGUNDA_FEIRA, TERCA_FEIRA, QUARTA_FEIRA, QUINTA_FEIRA, SEXTA_FEIRA, SABADO, DOMINGO }; int main() { //SEGUNDO PROBLEMA //Os enumeradores são conversiveis para inteiro, possibilitando assim que existam operações sem //sentido lógico, como no exemplo abaixo. //O que tem haver a pessoa somar um dia da semana com uma fruta???? //cout << DOMINGO + PERA << endl; //Enumerações com escopo //int a = DOMINGO + PERA; Erro, nao pode mais fazer isso. Frutas_Com_Escopo FR; FR = Frutas_Com_Escopo::BANANA; //int a = Frutas_Com_Escopo::BANANA; //Erro, agora a conversão deve ser explicita. //int a = (int)Frutas_Com_Escopo::BANANA; return 0; } Descobrir o tipo das enumerações Para se descobrir qual o tipo de dados utilizado por uma enumeração podemos utilizar o template std::underlying_type<enum>::type, fazendo-se necessário a inclusão do header <type_traits>. #include <iostream> #include <type_traits> using namespace std; enum Frutas {}; enum Semana : char {}; int main() { cout << boolalpha << is_same<unsigned int, underlying_type<Frutas>::type>::value << '\n'; cout << is_same<char, underlying_type<Semana>::type>::value << '\n'; return 0; } Declaração posterior de enum Podemos declarar o protótipo de um enum e declara-lo posteriormente, desde que seu tamanho tenha sido definido. #include <iostream> using namespace std; /* //Erro enum Frutas; enum Frutas { BANANA, PERA }; */ //Correto enum Frutas : char; enum Frutas : char { BANANA, PERA }; //Correto (int por padrao) enum class Frutas_2; enum class Frutas_2 { BANANA, PERA }; int main() { return 0; } Suporte a unicode Dois novos tipos foram adicionados para o suporte Unicode char16_t nos garante pelo menos 16 bits de tamanho e char32_t nos garante pelo menos 32 bits. Literais para o tipo char16_t e char32_t são definidos prefixando o literal da seguinte forma: char16_t c16 = u'A'; e char32_t c32 = U'A'; Literaisstring também podem ser usadas na codificação UTF-8 utilizando-se o prefixo u8: const char *p = u8"String UTF-8"; Existem também typedefs para as strings que utilizam codificação diferente: std::string → std::basic_string<char> std::wstring → std::basic_string<wchar> std::u16string → std::basic_string<char16_t> std::u32string → std::basic_string<char32_t> ã #include <iostream> using namespace std; int main() { char16_t unicode16 = u'A';//u representa char16_t; char32_t unicode32 = U'a';//u representa char32_t; //Literais string u8 const char *p = u8"String UTF-8"; return 0; } Suporte literais string raw Basicamente uma string raw é uma string onde caracteres como („\n‟, „t‟, „\‟, etc...) não são processados. Uma string raw inicia com R”(e termina com)”. Os delimitadores podem ser personalizados, mas não podem conter espaços em branco #include <iostream> using namespace std; int main() { string str = R"(\n)"; string s = R"!!!(ssssss)!!!";//Delimitador personalizado const char16_t *p = uR"(aaa)"; cout << str; cout << '\n'; cout << s; cout << '\n'; return 0; } Nova sintaxe para funções O novo padrão da linguagem C++ nos permite definir funções de uma nova forma: auto funcao()-> double, auto funcao()-> int. A primeira coisa a se colocar é a palavra auto, em seguida o nome da função, abrir parênteses e colocar ou não parâmetros para a função, fechar parênteses, colocar o operador -> e por ultimo dizer qual será o tipo de retorno da função. Sua principal utilização é para templates que será mostrada posteriormente. #include <iostream> using namespace std; //Retorna o menor entre 2 doubles auto menorEntreDoisDoubles(double a, double b) -> double { return a < b ? a : b; } //Retorna o menor entre 2 ints auto menorEntreDoisInts(int a, int b) -> int { return a < b ? a : b; } //Imprime Funcao!!! auto funcao() -> void { cout << "\nFuncao!!!\n"; } int main() { cout << menorEntreDoisDoubles(1.2, 2.2) << '\n'; cout << menorEntreDoisInts(1, 2) << '\n'; funcao(); return 0; } Lambdas Lambdas são um dos recursos mais aguardados do C++, lambdas são funções sem nome que você pode escrever inline em seu código fonte e podem ser utilizadas em locais onde funções tradicionais seriam usadas. A sintaxe para a criação das lambdas é a seguinte: [captura](parametros)mutable exception attribute -> retorno {corpo} [captura](parametros) -> retorno {corpo} [captura](parametros) {corpo} [captura]{corpo} Uma lambda embora não possua um nome pode ser chamada através de uma variável auto. #include <iostream> using namespace std; double menor(double a, double b) { return a < b ? a : b; } // lambdaMenor armazena a lambda auto lambdaMenor = [](double a, double b) -> double {return a < b ? a : b;}; int main() { cout << menor(10, 20) << "\n\n"; cout << lambdaMenor(10, 20) << '\n'; return 0; } Exemplo 2: #include <iostream> #include <string> #include <vector> #include <algorithm> using namespace std; //Classe teste com o operator () escrito (Functor) class Teste { public: void operator ()(int n) { cout << n << '\n'; } }; void imprime(int n) { cout << n << '\n'; } int main() { vector<int> v1; for(int n = 0; n <= 10; ++n) v1.push_back(n); //Chamando função for_each(v1.begin(), v1.end(), &imprime); //Chamando functor for_each(v1.begin(), v1.end(), Teste()); //Lambda for_each(v1.begin(), v1.end(), [](int n) {cout << n << '\n';} ); return 0; } Exemplo 3 #include <iostream> #include <string> #include <vector> #include <algorithm> using namespace std; //Objeto função(functor) class Teste { public: void operator ()(int n) { cout << n << '\n'; } }; //Função imprime void imprime(int n) { cout << n << '\n'; } int main() { vector<int> v1; //Adicionando elementos ao vetor for(int n = 0; n <= 10; ++n) v1.push_back(n); for_each(v1.begin(), v1.end(), &imprime); for_each(v1.begin(), v1.end(), Teste()); int j = 3; static int k = 4; //[] acessa variaveis externas no lambda //Caso queria colocar mais de 1 variavel j,v,l,p //Caso queira pegar todas = (pega por valor) //for_each(v1.begin(), v1.end(), [=](int n) {cout << n * j * k << '\n';} ); //Caso queira pegar todas & (pega por referencia) //for_each(v1.begin(), v1.end(), [&](int n) {cout << n * j * k << '\n';} ); //Quando a variavel eh static ou global nao precisa capturar pois são capturadas por default for_each(v1.begin(), v1.end(), [&j](int n) {cout << n * j * k << '\n';} ); return 0; } Callable objects Um callable object é qualquer coisa que pode ser chamado como uma função. Funções Lambdas Functors (objetos função) Para armazenar um objeto função a biblioteca padrão C++ nos fornece o objeto std::function que esta declarado dentro do arquivo header <functional>. #include <iostream> #include <functional> using namespace std; class Matematica { public: int operator ()(int a, int b) { return a + b; } }; int soma(int a, int b) { return a + b; } void imprimeHello() { cout << "Hello!!!" << '\n'; } int main() { //Armazenando função function<int(int, int)> f1 = soma; //Armazenando functor function<int(int, int)> f2 = Matematica(); //Armazenando lambda function<int(int, int)> f3 = [](int a, int b){return a + b;}; cout << "Funcao: " << f1(10, 90) << '\n'; cout << "Functor: " << f2(10, 90) << '\n'; cout << "Lambda: " << f3(10, 90) << '\n'; return 0; } Type alias No novo padrão do C++ podemos usar a declaração using para criar sinônimos para tipos no lugar de typedef. #include <iostream> #include <vector> using namespace std; //Cria sinonimos para vector<int> e vector<double> using vecInt = vector<int>; using vecDouble = vector<double>; typedef vector<int> vec; int main() { vecInt i; i.push_back(1); cout << i[0] << '\n'; vecDouble d; d.push_back(1.1); cout << d[0] << '\n'; return 0; } #include <iostream> #include <vector> using namespace std; template <class vec> using vetor = std::vector<vec>;int main() { vector<char> str; str.push_back('A'); str.push_back('G'); str.push_back('I'); str.push_back('T'); for(auto it: str) cout << it; return 0; } Inicialização uniforme No padrão anterior de C++ (C++ 98) haviam varias maneiras de se inicializar uma variável, entretanto no novo padrão C++ 11 foi introduzido uma nova forma de inicialização a inicialização uniforme #include <iostream> using namespace std; int main() { int vetor[]{1, 2, 3, 4, 5}; int vetor_2[] = {1, 2, 3, 4, 5};//Podemos usar = na inicialização int a{0}; int b = {0}; cout << "Inicializacao: int vetor[]{1, 2, 3, 4, 5};\n"; for(int i = 0; i < 5; ++i) cout << vetor[i] << '\n'; cout << "\n\nInicializacao: int vetor_2[] = {1, 2, 3, 4, 5};\n"; for(int i = 0; i < 5; ++i) cout << vetor_2[i] << '\n'; cout << "\n\nInicializacao: int a{0};\n" << a; cout << "\n\nInicializacao: int b = {0};\n" << b; cout << "\n\n"; return 0; } Listas inicializáveis No novo padrão C++ 11 foi introduzido o template std::initializer_list<> para armazenar uma coleção de valores que devem ser do mesmo tipo, assim como os arrays. Para armazenarmos valores nessa lista usamos as chaves {}. Para percorrermos um initializer_list usamos iterator assim como qualquer outra classe container. As construtoras de classes que recebem um initializer_list como parametro tem precedência sobre as outras. #include <iostream> #include <initializer_list> using namespace std; class Inicializa { public: //Construtora que recebe dois ints e exibe a soma Inicializa(int a, int b){cout << "\n***Inicializa(int a, int b)***\n" << a + b << '\n';} //Construtora que recebe uma lista de inicialização e imprime os itens recebidos na tela Inicializa(initializer_list<int> l) { cout << "\n***Inicializa(initializer_list<int> l)***\n"; //Percorre os elementos da lista e exibe na tela for(auto it = l.begin(); it < l.end(); it++) cout << *it << '\n'; } }; int main() { Inicializa s1{1, 2};//Chama Inicializa(initializer_list<int> l) Inicializa s2(10, 20);//Chama Inicializa(int a, int b) return 0; } A classe vector também possui uma construtora initializer_list. #include <iostream> #include <initializer_list> #include <vector> using namespace std; int main() { //Nessa primeira declaração estamos dizendo a classe vector que queremos //50 elementos de numeros inteiros contendo o valor 10 em cada um deles vector<int> v1(5, 10); //a classe vector tambem possui um initializer list e nesse caso estamos dizendo //a classe vector que queremos 2 elementos inteiros, um com o valor 5 e outro com valor 10 vector<int> v2{5, 10}; for(auto it = v1.begin(); it < v1.end(); it++) cout << *it << '\n'; cout << "\n"; for(auto it = v2.begin(); it < v2.end(); it++) cout << *it << '\n'; return 0; } Referencias rvalues Antes de começarmos a falar sobre rvalue e lvalue primeiro devemos entender o que eles são. Um lvalue é tudo aquilo que pode receber uma atribuição como, por exemplo: int a = 5; “a” é o valor que esta a esquerda da operação e “a” por ser uma variável pode receber uma atribuição portanto “a” sempre será um lvalue independente da posição em que esteja, seja do lado direito ou esquerdo, já o valor 5 é um rvalue, pois 5 é uma memória temporária que esta a direita da operação. A referencia rvalue pode referenciar uma memória temporária, ao contrario da referencia tradicional que só pode referenciar um lvalue. As regras para as referencias rvalues continuam sendo as mesmas que para as lvalues, ou seja, não podem referenciar outra região de memória e devem sempre ser inicializadas. A referência rvalue tem dois objetivos principais: A semântica move que permite mover o estado de objetos; Encaminhamento perfeito (perfect forwarding): que permite que um template de função repasse os parâmetros recebidos da mesma forma, lvalue para lvalue e rvalue para rvalue. #include <iostream> using namespace std; //Passagem de parametros por referencia lvalue int soma(int &a, int &b) { return a + b; } //Passagem de parametros por referencia Rvalue int somaComRvalue(int &&a, int &&b) { return a + b; } int main() { int a; int &b = a;//Referencia lvalue //int &c = 10;//Erro, nao pode referenciar um rvalue int &&d = 10;//No novo padrão podemos referenciar um rvalue int x = 10, y = 20; //cout << soma(10, 20) << '\n';//Erro pois somos obrigados a passar um lvalue cout << "Soma Lvalue: " << soma(x, y) << '\n';//Correto cout << "Soma Rvalue: " << somaComRvalue(10, 20) << '\n';//Agora podemos passar valores temporarios return 0; } Semântica move Através da semântica move, o estado de um objeto pode ser movido a outro, ao invés de ser copiado. Isso pode trazer grandes ganhos de performance quando os objetos consomem grandes quantidades de memória alocadas dinamicamente.. Como exemplo, podemos usar um vetor alocado no heap. Copiar este vetor, significa alocar uma nova memória do mesmo tamanho e copiar elemento por elemento. Mover, significa que podemos pegar o ponteiro do objeto armazenado pela origem e mover para o destino, não sendo necessário a alocação de uma nova memória e também a cópia individual de cada elemento. #include <iostream> #include <utility> #include <vector> #include <string> int main() { std::string str = "Ola mundo"; std::vector<std::string> v; //Aqui estamos usando //v.push_back(str); std::vector<std::string>::push_back(value_type &v) //Que terá o custo de copia da string str para dentro do vetor std::cout << "Depois da copia, str eh: " << str << "\n"; //Aqui estamos usando a semantica move, o que significa que nenhuma string será copiada. //o conteudo da string sera movido para dentro do vetor, ou seja, o vetor std::vector<std::string> v; //agora ira conter "Ola mundo" e str ficara vazia v.push_back(std::move(str)); std::cout << "Depois do move, str eh: " << str << "\n"; return 0; } #include <iostream> using namespace std; //Classe vetor class Vetor { //P será um vetor de elementos inteiros que terá seu tamanho definido assim que instanciar um objeto da classe vetor int *p; size_t lenght; public: //Construtora Vetor(size_t len) { p = new int[len]; lenght = len; } //Construtora de cópia por referencia Vetor(Vetor &v) { //Aloca p p = new int[v.lenght]; //Copia elemento por elemento for(int n = 0; n < v.lenght; ++n)p[n] = v.p[n]; lenght = v.lenght; cout << "Copia\n"; } //Construtora move, move os dados de os dados temporarios de uma classe para outra. Vetor(Vetor &&v) { cout << "Move\n"; //Ao inves de copiar elemento por elemento fazemos com que p da nova instancia aponte para p da outra instancia p = v.p; lenght = v.lenght; v.p = nullptr; v.lenght = 0; } //Sobrecarga do operador [] para podermos acessar os index int &operator [] (int index) { return p[index]; } //Destrutora ~Vetor() { delete []p; p = nullptr; } }; //Cria um vetor temporario de 5 elementos Vetor func() { Vetor v(5); v[0] = 1; return v; } //Cria um vetor de 5 elementos Vetor *func_2() { Vetor *v = new Vetor(5); (*v)[0] = 10; return v; } int main() { //Chama a construtora move Vetor v2(move(func())); //Chama a construtora de cópia Vetor v3(*func_2()); return 0; } Perfect forward Utilizado em templates de função para manter o estado lvalue ou rvalue quando repassar os argumentos a outras funções. É implementado através de std::forward. #include <iostream> using namespace std; //Class a com construtor que recebe um rvalue e um lvalue struct A { A(int&& n) { std::cout << "rvalue overload, n=" << n << "\n"; } A(int& n) { std::cout << "lvalue overload, n=" << n << "\n"; } }; //Instancia um objeto da classe especificada no template que recebe um parametro especificado //&& && = && //&& & = & //& && = & //& & = & template<class T, class U> T* instancia_objeto(U&& u) { //forward nos garante que o parametro sera passado exatamente como veio, ou seja, rvalue sera passado como rvalue //e lvalue sera passado como l value return (new T(forward<U>(u))); } int main() { A* p1 = instancia_objeto<A, int&&>(2); // rvalue int i = 1; A* p2 = instancia_objeto<A, int&>(i); // lvalue return 0; } Funções membro default Por padrão, algumas funções especiais são acrescentadas às classes quando necessário, estas funções são: - Construtora padrão; - Construtora de cópia; - Construtora move; - Operador de atribuição para cópia; - Operador de atribuição para mover; - Destrutora. Estas funções são declaras: public/inline e não explicitas. Podemos especificar em nossas classes que estas funções devem usar a implementação padrão explicitamente: Exemplo: #include <iostream> using namespace std; class Teste { public: Teste(int i) { } Teste() = default; }; int main() { Teste a; return 0; } Na classe acima, se não tivéssemos declarado a construtora padrão como default, ela não existiria e somente a construtora que recebe um int poderia ser usada. Funções delete Podemos deletar qualquer função através da declaração delete, ao invés de colocar as mesmas como membros privates usamos o delete. O uso mais comum para delete é não permitir a cópia ou mover um objeto. #include <iostream> using namespace std; class teste { public: teste( const teste &t ) = delete; teste & operator = ( const teste & ) = delete; teste() = default; }; int main() { teste a; return 0; } Note que se você deletar as operações de cópia, estará automaticamente deletando as operações de mover e vice-versa. Como dito, qualquer função pode ser deletada: void func( void *p ) {} void func( const char *) = delete; Funções virtuais podem ser deletadas, e neste caso todas as funções desde a classe base serão deletadas. Inicialização de membros in-class Podemos inicializar qualquer membro in-class sem que haja a necessidade de inicializar na lista de inicialização ou na construtora da classe. #include <iostream> using namespace std; class teste { int a = 5; int b { 3 }; //int c(0); // erro – não pode usar inicialização direta int d = a*b; // ok //static int e = 3; // erro, deve ser inicializada fora const int f = 6; public: teste(int var) : a(var) {} // prevalece a lista de inicialização teste() = default; }; int main() { teste h; return 0; } As inicialização in-class elimina o código redundante de inicialização das construtoras. Útil quando os membros devem ter o mesmo valor de inicialização independente da construtora. Inicializar um membro in-class faz com que o tipo se torne não agregado. Delegando construtoras Uma construtora pode delegar a inicialização de outra construtora, ou seja, ao uma construtura ser chamada pode disparar outra, só não pode delegar ela mesma, não pode conter mais nada na lista de inicialização, uma construtora pode delegar outra que delega outra e assim por diante, o corpo da construtora que delegou será executado assim que a construtora delegada terminar. #include <iostream> using namespace std; class Teste { int a, b; public: //Delegando (chamando outra construtora //OBS: Nao pode conter nenhuma outra inicicializacao a nao ser de construtoras Teste() : Teste(0) { cout << "Sem paramentro\n\n"; } Teste(int a) : Teste(0, 0) { cout << "Com 1 paramentros\n\n"; } Teste(int Pa, int Pb) : a(Pa), b(Pb) { cout << "Com 2 paramentros\n\n"; } }; int main() { Teste t; return 0; } Final e override As palavras final e override são palavras reservadas dependendo do contexto onde aparecem. final: pode ser usada para uma classe ou para uma função virtual. Se usado para uma classe, indica que esta não pode ser herdada. Já para uma função virtual indica que ela não pode ser redefinida na classe derivada. override: Usado para informar que a função está sobrecarregando uma virtual da classe base. Final #include <iostream> using namespace std; class Base final { public: int a; }; /*Gera erro de compilação, pois base não pode ser herdada class Derivada : public Base { public: int b; };*/ class VirtualFinal { public: virtual void f() final{} }; /*Gera erro de compilação, pois f() não pode ser redefinida class DerivadaVirtualFinal: public VirtualFinal { public: void f(){} };*/ int main() { return 0; } Override #include <iostream> using namespace std; struct Base { virtual void f() { cout << "F Base.\n"; } public: void executaF() { f(); } }; struct Derivada : Base { //Indica que estamos reescrevendo a virtual da base virtual void f() override{ cout << "Derivada override.\n"; } }; int main() { Derivada de; de.executaF(); return 0; } Static assert Executa uma verificação em tempo de compilação para ver se a condição especificada é verdadeira, ou falsa. A sintaxe para static_assert é a seguinte: static_assert(condição, mensagem de erro); Caso a condição retorne false, teremos erro de compilação e a mensagem de erro especificada no segundo parâmetro será escrita. #include <iostream> using namespace std; template<typename Tipo, unsigned int Tamanho> class Vetor { Tipo m_Element[Tamanho]; public: Vetor() { //Se o vetor for menor ou igual a 10, a condição é true e esta correto. //Caso contrário é false e teremos erro de compilação static_assert(Tamanho <= 10, "Não pode criar um vetor com mais de 10 elementos."); } }; int main() { Vetor<int, 10> vec;//Correto, Tamanho <= 10 = true Vetor<int, 11> vec_2;//Incorreto, Tamanho <= 10 = false; return 0; } Operadores de conversão explicit Em C++ quando criamos uma classe, podemos criar nossos próprios operadores de cast. O problema é que esses cast podem ser executados de forma implícita, dificultando a leitura do código, para corrigir isso C++ 11 introduziu os operadores de conversão explicit, onde podemos forçar uma conversão explicita. #include <iostream> using namespace std; class converteImplicito { public: int m_var = 0; //Operadores de conversão implicitos operator int() { return m_var; } }; class converteExplicito { public: int m_var = 0; //Operadores de conversão explicitos explicit operator int() { return m_var; } }; int main() { converteImplicito convImpl, convImpl_2; int a = 10; cout << "a antes: " << a << '\n'; //Isso nos deixa confuso. //convImpl é um int ou é uma classe??? a = convImpl; cout << "a depois: " << a << '\n'; //Como assim?? o que estamos comparando?? if(convImpl == convImpl_2) cout << "convImpl e convImpl_2 sao iguais\n\n"; converteExplicito convExpl, convExpl_2; int b = 10; cout << "b antes: " << b << '\n'; //Somos obrigados a forçar o cast explicitamente, deixando claro que estamos convertendo pra int b = int(convExpl); cout << "b depois: " << b << '\n'; //Somos obrigados a forçar o cast explicitamente, deixando claro que estamos convertendo pra int if(int(convExpl) == int(convExpl_2)) cout << "convExpl e convExpl_2 sao iguais\n\n"; return 0; } Decltype Retorna o tipo de uma expressão sem avaliá-la. O uso mais comum para decltype é para especificar o tipo de retorno de um template quando este depende dos tipos do parâmetros. #include <iostream> using namespace std; //Dependemos que o programador passe corretamente os tipos, tendo em mente que ele sempre //Passara o tipo maior primeiro, mas isso pode não acontecer template<typename MAIOR, typename MENOR> MAIOR RetornaMaior(MAIOR mai, MENOR men) { return mai > men ? mai : men; } //decltype(mai + men) ira retornar o tipo do maior template<typename MAIOR, typename MENOR> auto RetornaMaiorDecltype(MAIOR mai, MENOR men) -> decltype(mai + men) { return mai > men ? mai : men; } int main() { //Funciona perfeitamente cout << "Sucesso RetornaMaior<double, int>(19.99, 10): " << RetornaMaior<double, int>(19.99, 10) << '\n'; //Funciona erroneamente, pois truca o valor cout << "Falha RetornaMaior<int, double>(10, 19.99): " << RetornaMaior<int, double>(10, 19.99) << '\n'; //Funciona perfeitamente cout << "Sucesso RetornaMaiorDecltype<int, double>(10, 19.99): " << RetornaMaiorDecltype<int, double>(10, 19.99) << '\n'; return 0; } Templates variadics Os templates agora podem ter uma quantidade variável de argumentos. template<typename ...Args> class Sample; template<typename T, typename ...Args> T funcao(Args …args); Em uma lista de parâmetros de função, o parâmetro a ser expandido deve aparecer por último. Geralmente, a manipulação de um template variadic é feita através de recursividade, onde se analisa o primeiro parâmetro e passa o resto dos argumentos para o template novamente. As únicas operações que podem ser feitas sobre a lista de parâmetros variáveis são (…) para se referir à lista e sizeof...() para descobrir a quantidade de elementos na lista. Exemplo 1: #include <stdexcept> #include <iostream> using namespace std; //Conta numero de parametros do template template <class ...A> int func(A... arg) { return sizeof...(arg); } int main(void) { cout << func(1,2,3,4,5,6) << '\n'; return 0; } Exemplo 2: Essa declaração pode parecer estranha. Isso porque argumentos de modelo variadics são percorridos de forma recursiva, em vez de forma iterativa. Abordamos o primeiro argumento da função, e depois uma chamada a função usando o resto dos argumentos. Observe como nós definimos duas sobrecargas de função aqui. Uma das sobrecargas de função não tem argumentos porque C + + precisa saber como terminar a recursão. Se essa sobrecarga não for especificada, então C++ vai dar um erro quando não encontrar uma versão da soma que não leva argumentos. #include <iostream> using namespace std; //Repare que para capturarmos o parametro real da função fomos obrigados a criar //uma sobrecarga da função func e se retirarmos void func(){} o programa não compila void func(){} template <typename Valor, class ...Parametros> void func(Valor cabeca, Parametros... calda) { cout << "A: " << cabeca << endl; //Chama a propia função passando parametro por parametro capturado //Quando chegar ao ultimo parametro C++ precisa encontrar uma função com mesmo nome //Que não leve parametros para saber onde tudo termina, caso contrario teremos erro de compilação func(calda...); } int main(void) { func(1,2,3,4,5,6); return 0; } Controle de Alinhamento Muitas instruções executadas pelos processadores atuais, requerem que os dados sejam alinhados na memória e além disso, alinhar dados frequentemente utilizados no cache pode aumentar a performance. Antes do C++11, eram os compiladores que forneciam maneiras específicas para modificar ou consultar informações sobre o alinhamento. Agora podemos fazer isso de forma padronizada usando alignof() e alignas(). #include <iostream> using namespace std; struct Alinhamento { double a; int b; short c; bool d; char e; }; int main() { //Alinhamento default, alinhou pelo maior tipo ,ou seja, double 8 bytes cout << sizeof(Alinhamento) << '\n'; //Pergunta o alinhamento em bytes cout << alignof(Alinhamento) << '\n'; //Mudamos o alinhamento de memoria. alignas(4) Alinhamento a; cout << sizeof(a) << '\n'; cout << alignof(a) << '\n'; return0; } Literais definidos pelo usuário C + +11 introduziu a capacidade de adicionar sufixos personalizados para literais, a fim de fornecer valores diferentes. Sufixos literais podem ser sobrecarregados de uma forma muito semelhante aos operadores. Através dos literais definidos pelo usuário, qualquer tipo de dados pode ter sua representação literal. #include <iostream> using namespace std; long double operator"" _POW(long double BASE) { return BASE * BASE; } int main() { double x = 2.0_POW; cout << x << '\n'; return 0; } Namespace inline Namespaces servem para evitar a coalizão de nomes em projetos grandes. Os nomes declarados dentro de um namespace ficam dentro de um escopo, o que previne de haver coalizões com outros nomes iguais e outras partes do projeto. Em C++ 11 foram inseridos os namespaces inline que servem para tornar o uso de um namespace opcional. #include <iostream> using namespace std; namespace Sample { //Torna o uso de inner opicional inline namespace Inner { int a; } } int main() { //Da no mesmo Sample::Inner::a = 1; Sample::a = 1; return 0; } Smart pointers O principal uso de smart pointers no C++ é simplificar o gerenciamento de memórias alocadas dinamicamente, principalmente nos momentos em que ocorrem exceções. Para isso, foram adicionados os seguintes smart pointers: - std::shared_ptr<> - std::weak_ptr<> - std::unique_ptr<> Além do já existente std::auto_ptr<> que pelas suas limitações é considerado obsoleto. Todos esses smart pointers são declarados no arquivo <memory>. Shared ptr Permitem compartilhar uma memória alocada por N ponteiros e guarda um contador de referencias que é incrementado em 1 a cada novo ponteiro que compartilhar a mesma memória. Quando o contador chegar a 0 a memória será desalocada automaticamente. #include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> pI(new int(0)); cout << "Contador: " << pI.use_count() << "\n\n"; { shared_ptr<int> pI_2(pI); cout << "Contador: " << pI_2.use_count() << "\n\n"; } cout << "Contador: " << pI.use_count() << "\n\n"; return 0; } Weak Ptr std::weak_ptr<>: Usado em conjunto com um std::shared_ptr<> pode dizer se o ponteiro continua ou não válido. Quando o contador de um std::shared_ptr<> chegar a zero, todos os std::weak_ptr<> relacionados a eles expiram. #include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> pI_shared(new int(0)); cout << "Contador: " << pI_shared.use_count() << '\n'; //Não aumenta o contador weak_ptr<int> pI_weak(pI_shared); cout << "Contador: " << pI_shared.use_count() << '\n'; //Verifica se o ponteiro não esta expirado if(!pI_weak.expired()) cout << "Nao expirado!!!\n"; else cout << "Expirado!!!\n"; cout << "Contador: " << pI_shared.use_count() << '\n'; //Expira o ponteiro pI_shared.reset(); //Verifica se o ponteiro não esta expirado if(!pI_weak.expired()) cout << "Nao expirado!!!\n"; else cout << "Expirado!!!\n"; return 0; } Um std::weak_ptr<> não pode na verdade ser usado com um smart pointer, pois, não possui os operadores de * e → que permitem o acesso ao ponteiro. Para usar um std::weak_ptr<>, temos que criar um std::shared_ptr<> a partir dele. OBS: Se o std::weak_ptr estiver inválido, então o std::shared_ptr irá conter um ponteiro nulo. #include <iostream> #include <memory> using namespace std; int main () { //Apenas cria shared_ptr, sem instanciar nada no heap shared_ptr<int> sp1,sp2; //Apenas cria shared_ptr, sem apontar pra nenhum shared_ptr weak_ptr<int> wp; //Instancia um int no heap e faz sp1 apontar para esse int sp1 = std::make_shared<int> (20); wp = sp1; //agora weak_ptr wp aponta para sp1 sp2 = wp.lock(); //Retorna o shared_ptr sp1.reset(); //Resseta o ponteiro sp1 = wp.lock(); //Poe novamente o int alocado no heap std::cout << "*sp1: " << *sp1 << '\n'; std::cout << "*sp2: " << *sp2 << '\n'; return 0; } Unique Ptr std::unique_ptr<> é o sucessor de std::auto_ptr<>, mas possui várias melhorias, como por exemplo pode armazenar ponteiros para arrays. Obs: Um std::unique_ptr<> não pode ser copiado, mas somente movido. #include <iostream> #include <memory> using namespace std; int main() { //Cria um unique_ptr que instancia um int no heap unique_ptr<int> u(new int(123)); cout << *u << '\n'; //Move o conteudo do ponteiro de u para u2 unique_ptr<int> u2(move(u)); //cout << *u << '\n';//Erro, porque o conteudo de um foi movido para u2, ou seja, u agora é nulo //unique_ptr<int> u3(i); Erro pois nao permite copia //Aloca um vetor de 10 inteiros no heap unique_ptr<int []> uv(new int[10]); //Insere dados no vetor for(int i = 0; i < 10; ++i) uv[i] = i; //Exibe os dados for(int i = 0; i < 10; ++i) cout << uv[i] << '\n'; return 0; } Novas classes containers std::forward_list<>: lista ligada unidirecional, declarado no arquivo <forward_list>; tabelas hash: Tipos de containers associativos que usam uma chave para localizar um elemento. Quatro templates podem ser usados: unordered_map<> e unordered_multimap<> declarados em <unordered_map> e unordered_set<> e unordered_multiset<> declarados em <unordered_set>. tuple<>: Container de tamanho fixo que armazena valores de tipos diferentes. Declarado em <tuple>. Para acessar um valor individual usa-se o template de função get<>() passando como argumento o índice do elemento desejado. std::array: Declarado em <array>, criado para armazenar um array de tamanho fixo com performance semelhante ao arrays da linguagem C. Forward list Lista ligada unidirecional, só é possível inserir elementos a partir do inicio. Exemplo 1 #include <iostream> #include <forward_list> #include <iterator> using namespace std; //Exibe conteudo da forward_list template<typename Tipo> void imprimeForward_List(forward_list<Tipo> li) { cout << "\n\n"; for(auto it: li) cout << it << '\n'; } int main() { //Monta forward_list com 5 elementos forward_list<int> list = {3, 4, 5, 6, 7}; //Exibe imprimeForward_List(list); //Insere antes do primeiro elemento list.insert_after(list.before_begin(), 1); imprimeForward_List(list); //Insere depois do primeiro elemento list.insert_after(list.begin(), 2); imprimeForward_List(list); //Insere antes do primeiro elemento list.push_front(0); imprimeForward_List(list); //list.insert_after(list.end(), 8); //Erro,não pode inserir elementos no fim return 0; } Exemplo 2 #include <forward_list> #include <iostream> #include <algorithm> #include <iterator> #include <string> using namespace std; //Imprime conteudo das listas void printLists(const string title, const forward_list<int>& l1, const forward_list<int>& l2) { cout << title << endl; cout << " list1: "; for(auto it: l1) cout << it << " "; cout << endl << " list2: "; for(auto it: l2) cout << it << " "; cout << endl; } int main() { //Cria duas forward_list forward_list<int> list1 = { 1, 2, 3, 4 }; forward_list<int> list2 = { 66, 77, 88, 99 }; //Exibe as listas printLists ("Inicio:", list1, list2); //Insere 55 antes do primeiro elemento (66) list2.insert_after(list2.before_begin(), 55); //Insere 44 antes do primeiro elemento (55) list2.push_front(44); //Insere 11, 22, e 33 antes primeiro elemento (44) list2.insert_after(list2.before_begin(), {11,22,33} ); printLists ("\n\n5 novos elementos em list2:", list1, list2); //Insere todos os elementos da lista2 dentro da lista 1 list1.insert_after(list1.before_begin(), list2.begin(), list2.end()); printLists ("\n\nInsere todos os elementos de list2 em list1:", list1, list2); //Deleta o segundo elemento da list2, ou seja, 22 list2.erase_after(list2.begin()); printLists ("\n\nDeleta o segundo elemento da list2, ou seja, 22:", list1, list2); //find irá apagar todos os elementos após 77 list2.erase_after(find(list2.begin(),list2.end(), 77), list2.end()); printLists ("\n\nDeleta o elemento 99 da list2:", list1, list2); //Reordena as posições list1.sort(); //copia list1 dentro de list2 list2 = list1; //Remove duplicações list2.unique(); printLists ("\n\nsorted e unique:", list1, list2); //Retira todos os elementos de list2 e insere em list1 list1.merge(list2); printLists ("\n\nmerged:", list1, list2); return 0; } Unordered map unordered_map são containners associativos que armazenam os elementos formados pela combinação de um valor e uma chave, e que permite a rápida recuperação de elementos individuais com base em suas chaves. #include <unordered_map> #include <string> #include <iostream> using namespace std; int main() { unordered_map<string, int> coll{ { "unordered_map_10", 10 }, { "unordered_map_20", 20 } }; //Imprime o elemento e a chave for (const auto& elem : coll) cout << elem.first << ": " << elem.second << endl; return 0; } Unordered multimap unordered_map são containners associativos que armazenam os elementos formados pela combinação de um valor e uma chave, assim como recipientes unordered_map, mas permitindo que os diferentes elementos tenham chaves equivalentes #include <unordered_map> #include <string> #include <iostream> using namespace std; int main() { unordered_multimap<string, int> coll{ {"unordered_map_10", 10}, {"unordered_map_10", 10} }; //Imprime o elemento e a chave for (const auto& elem : coll) cout << elem.first << ": " << elem.second << endl; return 0; } Unordered set Unordered_set são containers que armazenam elementos únicos em nenhuma ordem particular, e que permitem a rápida recuperação de elementos individuais com base em seu valor. #include <unordered_set> #include <string> #include <iostream> using namespace std; int main() { unordered_set<string> coll{"Numero: 111", "Quedinho", "Major", "Rua"}; //Imprime o elemento for (auto& it : coll) cout << it << " "; cout << "\n\n"; return 0; } Unordered multiset Unordered multiset são containners que armazenam elementos sem nenhuma ordem particular, permitindo rápida recuperação de elementos individuais com base em seu valor, assim como containners unordered_set, mas permitindo que os diferentes elementos tenham valores equivalentes. #include <unordered_set> #include <string> #include <iostream> using namespace std; int main() { unordered_multiset<string> coll{"Numero: 111", "Quedinho", "Major", "Rua", "Numero: 111", "Quedinho", "Major", "Rua"}; //Imprime o elemento for (auto& it : coll) cout << it << " "; cout << "\n\n"; return 0; } Tuple Tuples são objetos que armazenam elementos de tipos diferentes ou iguais em um único objeto. #include <tuple> #include <iostream> #include <complex> #include <string> using namespace std; int main() { //Cria uma tuple que armazena 5 valores string tuple<string, string, string, string, string> t("Herik", "22 Anos", "RG:00.000.000-00", "CPF:000.000.000-00", "11/08/1991"); //O Template de função get nos permite acessa cada elemento da tupla cout << get<0>(t) << ", "; cout << get<1>(t) << ", "; cout << get<2>(t) << ", "; cout << get<3>(t) << ", "; cout << get<4>(t) << "\n\n"; return 0; } Array Arrays são containners de tamanho fixo que armazenam um número específico de elementos ordenados em sequencia. #include <array> #include <iostream> #include <algorithm> #include <functional> #include <numeric> using namespace std; //Imprime todos os elementos void imprime(array<int, 10> &a) { for(auto &it : a) std::cout << it << '\n'; } int main() { //Cria um array de ints com 10 elementos array<int,10> a = { 11, 22, 33, 44 }; imprime(a); cout << "\n\nSoma de todos os elementos: " << accumulate(a.begin(),a.end(),0) << "\n\n"; imprime(a); return 0; } Conversão de strings Em C++ 11 foram inseridas uma serie de funções que convertem uma string para tipos numéricos. O primeiro parâmetro const string &, diz a string que vai ser convertida, O segundo parâmetro size_t *idx=0, vai armazenar o numero de dígitos do valor a ser convertido e o terceiro int base=0, diz qual base esta sendo convertida. int stoi(const string &, size_t *idx=0, int base=0); long stol(const string &, size_t *idx=0, int base=0); unsigned long stoul(const string &, size_t *idx=0, int base=0); long long stoll(const string &, size_t *idx=0, int base=0); unsigned long long stoull(const string &,size_t *idx=0,int base=0); float stof(const string &, size_t *idx=0, int base=0); double stod(conststring &, size_t *idx=0, int base=0); long double stold(const string &, size_t *idx=0, int base=0); #include <iostream> using namespace std; int main() { string str_hexa = "0xF"; string str_decimal = "10"; string str_octal = "012"; string str_bin = "1010"; size_t *quantidade = new size_t(0); //stoi, o primeiro parametro é a string que se quer converter, o segundo armazena o numero de digitos, //e o terceiro, diz a base que esta sendo convertida int a = stoi(str_decimal, quantidade , 16); cout << "Conversao(Hexa) de: " << str_hexa << " | para decimal: " << a << " | Quantidade de digitos: " << *quantidade << "\n\n"; a = stoi(str_decimal, quantidade , 10); cout << "Conversao(Deci) de: " << str_decimal << " | para decimal: " << a << " | Quantidade de digitos: " << *quantidade << "\n\n"; a = stoi(str_octal, quantidade , 8); cout << "Conversao(Octal) de: " << str_octal << " | para decimal: " << a << " | Quantidade de digitos: " << *quantidade << "\n\n"; a = stoi(str_bin, quantidade , 2); cout << "Conversao(Bin) de: " << str_bin << " | para decimal: " << a << " | Quantidade de digitos: " << *quantidade << "\n\n"; return 0; } Constexpr C++ 11 inseriu o constexpr, que permite que seus programas em C++ tirem vantagem em tempo de compilação das expressões constantes. Por exemplo, vamos imaginar que criemos uma função constexpr que calcule o fatorial e em algum ponto do código haja algo assim int fat = fatorial(5); Em um programa comum, o fatorial 5 serial calculado em tempo de execução, chamando a função e retornando seu valor, já com constexpr quando estivermos compilando nosso código, ele ira resolver isso, ou seja, irá calcular o fatorial de 5 em tempo de compilação e já vai “colocar” int fat = 120;, ou seja, quando estivermos executando o programa a função não será chamada, pois o calculo já foi feito durante a própria compilação. OBS: Constexpr só pode ter uma linha de código. #include <iostream> using namespace std; constexpr unsigned long long fatorial (int n) { return n > 0 ? n * fatorial( n - 1 ) : 1; } int main() { //Ira calcular em tempo de compilação sempre que possivel int fat = fatorial(5); cout << fat << '\n'; return 0; } Threads Um thread executa um callable object passado como primeiro argumento de forma assíncrona. Exemplo 1: #include <iostream> #include <thread> #include <mutex> using namespace std; mutex mu; void threadFunc() { //Bloqueia cout mu.lock(); //Escreve em cout cout << "Thread disparado.\n\n"; //Libera cout mu.unlock(); } int main() { int x = 10; //Dispara o thread thread th (threadFunc); th.join();//sincroniza os threads para que main não termine antes de threadFunc(); cout << "Thread main.\n" << '\n'; return 0; } Exemplo 2 #include <iostream> #include <thread> #include <windows.h> using namespace std; void pause_thread(int n) { //Por enquanto usaremos o sleep do windows, contido no header windows.h Sleep(n); std::cout << "Pausa de " << (n / 1000) << " segundos finalizada.\n"; } int main() { cout << "Disparando 3 threads de forma independente...\n"; thread (pause_thread,1000).detach(); thread (pause_thread,2000).detach(); thread (pause_thread,3000).detach(); cout << "O main thread vai parar por 5 segundos.\n\n"; pause_thread(5000); return 0; } Uma outra forma de executar uma função de forma assíncrona é através da função std::async(). A vantagem é que um thread iniciado desta forma pode retornar um valor ou uma exceção para o thread criador. #include <iostream> #include <thread> #include <future> using namespace std; double soma(double a, double b) { return a+b; } int main() { auto result = std::async(soma, 10.0, 20.0); cout << result.get() << '\n'; return 0; } No exemplo anterior, uma operação assíncrona foi iniciada através da chamada à função std::async. Quando precisarmos do valor de retorno da função, usamos a função get() do objeto retornado por async(). Mas, o que o método async retorna ? A resposta é um objeto do tipo std::future<> que foi criado para esta finalidade. A chamada à função async poderia ter sido feita assim: #include <iostream> #include <thread> #include <future> using namespace std; double soma(double a, double b) { return a+b; } int main() { future<double>result = std::async(soma, 10.0, 20.0); cout << result.get() << '\n'; return 0; } Exemplo 3: #include <iostream> #include <thread> #include <future>//Pega valores de retorno de um thread #include <vector> using namespace std; double calculo() { int t = 50; double result = 0; for(int n = 0; n < t; ++n) { result += n; } cout << "Thread id: " << this_thread::get_id() << " = " << result; return result; } int main() { vector<future<double>> vfuture; vfuture.resize(10); for(int n = 0; n < 10; ++n) vfuture[n] = async(calculo);//assincrono for(int n = 0; n < 10; ++n) cout << " | Future:[" << n << "] = " << vfuture[n].get() << '\n'; return 0; } Exemplo 4 #include <iostream> #include <thread> #include <mutex> #include <future> #include <vector> using namespace std; mutex mu; int calculoThread = 0; int calcula() { this_thread::yield();//Permite a execução de varios threads lock_guard<mutex>lock(mu); ++calculoThread;//Para diferenciar o valor de calculo de cada thread int result = 0; for(int i = 0; i < (10); ++i) result += i; cout << "Resultado gerado pelo thread: " << this_thread::get_id() << ", resultado: " << (result + calculoThread) << '\n'; return result + calculoThread; } int main() { vector<future<int>> vecFuture;//Cria vetor de future vecFuture.resize(10); for(int i = 0; i < 10; ++i) vecFuture[i] = async(launch::async, calcula);//Os threads serão disparados, calculados e future vai //Armazenar seu valor de retorno. //Para dar tempo de todos os threads serem processados this_thread::sleep_for(chrono::seconds(2)); for(int i = 0; i < 10; ++i)//Capturando o valor processado pelos threads atraves de future cout << "Pegando resultado[" << i << "]" << ", " << vecFuture[i].get() << '\n'; return 0; } Será que a função async executou mesmo a função soma() do exemplo anterior de forma assíncrona? Pode ser. Isso porque neste caso, a implementação decide se deve iniciar a execução imediatamente, ou se é melhor esperar até que o método get() seja chamado para executar a função soma() de forma síncrona. Podemos escolher como executar o callableobject passando um argumento a mais para a função std::async(): auto result = std::async(std::launch::async,....); // assíncrono auto result = std::async(std::launch::deferred,...);// síncrono Mutex A execução concorrente entre threads pode ser um problema quando eles precisam acessar dados compartilhados entre eles. Em um determinado momento os threads poderão acessar simultaneamente esses dados, e obter resultados corrompidos. Para resolver esse tipo de problema, podemos usar mutex (exclusão mutual), que bloqueia o acesso a um recurso enquanto necessário e desbloqueia quando terminar. As classes disponíveis para mutex são: std::mutex std::timed_mutex; std::recursive_mutex; std::recursive_timed_mutex; Geralmente, um mutex é gerenciado por uma das classes RAII (Resource Acquisition is Initialization) da biblioteca padrão, são elas: std::lock_guard<>: Bloqueia o mutex na construtora e desbloqueia na destrutora. Nenhuma outra operação é permitida. std::unique_lock<>: Mais flexível que o std::lock_guard<>, pode adquirir o travamento sobre o mutex depois de construído e desbloquear antes da destrutora, pode ser movido,entre outras coisas. Mutex Não pode fazer dois locks simultâneos. #include <iostream> #include <thread> #include <mutex> using namespace std; mutex mu; void escreva() { for(int i = 0; i <= 10; ++i) { //cout esta sendo compartilhado por 2 threads e por isso devemos colocar o mutex aqui. mu.lock(); cout << "Thread: " << this_thread::get_id() << " imprimindo: " << i << '\n'; //E destravar aqui. mu.unlock(); } } int main() { thread th1(&escreva), th2(&escreva); th1.join(); th2.join(); return 0; } recursive_mutex Podemos fazer dois locks simultâneos. #include <iostream> #include <thread> #include <mutex> using namespace std; recursive_mutex mu; void escreva() { for(int i = 0; i <= 10; ++i) { //cout esta sendo compartilhado por 2 threads e por isso devemos colocar o recursive_mutex aqui. mu.lock(); mu.lock(); cout << "Thread: " << this_thread::get_id() << " imprimindo: " << i << '\n'; //E destravar aqui. mu.unlock(); mu.unlock(); } } int main() { thread th1(&escreva), th2(&escreva); th1.join(); th2.join(); return 0; } Timed_mutex Tenta travar o mutex por um determinado tempo. #include <iostream> #include <thread> #include <mutex> #include <chrono> using namespace std; timed_mutex mu; void escreva() { auto now = std::chrono::steady_clock::now(); for(int i = 0; i <= 10; ++i) { //Tenta travar por 2 segundos mu.try_lock_until(now + std::chrono::seconds(2)); cout << "Thread: " << this_thread::get_id() << " imprimindo: " << i << '\n'; //E destravar aqui. mu.unlock(); } } int main() { thread th1(&escreva), th2(&escreva); th1.join(); th2.join(); return 0; } Recursive_timed_mutex É a união de timed_mutex com recursive_mutex. #include <iostream> #include <thread> #include <mutex> #include <chrono> using namespace std; recursive_timed_mutex mu; void escreva() { auto now = std::chrono::steady_clock::now(); for(int i = 0; i <= 10; ++i) { //Tenta travar por 2 segundos mu.try_lock_until(now + std::chrono::seconds(2)); mu.try_lock_until(now + std::chrono::seconds(2)); cout << "Thread: " << this_thread::get_id() << " imprimindo: " << i << '\n'; //E destravar aqui. mu.unlock(); mu.unlock(); } } int main() { thread th1(&escreva), th2(&escreva); th1.join(); th2.join(); return 0; } lock_guard Quando criamos um mutex corremos o risco de que haja um IF, um return, ou até mesmo uma exceção no meio do caminho antes de chegar no unlock, isso causaria dead lock no programa. Para evitar isso foi criado o lock_guard, que trava o mutex na construtora e destrava na destrutora, garantindo assim que o mutex sempre será travado e depois destravado. #include <iostream> #include <thread> #include <mutex> using namespace std; mutex mu; void escreva() { for(int i = 0; i <= 10; ++i) { //lock_guard travará mu na construtora lock_guard<mutex> lockGuard(mu); cout << "Thread: " << this_thread::get_id() << " imprimindo: " << i << '\n'; } //E vai destravar aqui. } int main() { thread th1(&escreva), th2(&escreva); th1.join(); th2.join(); return 0; } unique_lock Nos da liberdade para destrava/travar quando quisermos, mas é garantido que sempre que passar pela destrutora será destravado. #include <iostream> #include <thread> #include <mutex> using namespace std; mutex mu; void escreva() { for(int i = 0; i <= 10; ++i) { //lock_guard travará mu na construtora unique_lock<mutex> uniqueLock(mu); cout << "Thread: " << this_thread::get_id() << " imprimindo: " << i << '\n'; //Nos da liberdade para destrava/travar quando quisermos, mas é garantido que sempre que, //passar pela destrutora será destravada. uniqueLock.unlock(); } } int main() { thread th1(&escreva), th2(&escreva); th1.join(); th2.join(); return 0; } Future/Promise Vimos anteriormente, que um objeto std::future<> é utilizado para pegar o valor retornado por uma função executada por std::async<>. Mas esta não é a única maneira de se disponibilizar dados para um objeto std::future<>. Podemos também usar um objeto std::promise<> para esta finalidade. Cada objeto std::promise<> esta ligado exclusivamente a um objeto std::future<>. O thread que tiver acesso ao objeto std::future<> pode esperar até que os dados estejam prontos, enquanto o thread que tem acesso ao objeto std::promise correspondente ao std::future<> pode chamar o método set_value() para disponibilizar os dados. Exemplo 1 #include <iostream> #include <thread> #include <future> using namespace std; template<typename T> void ImprimeFuture(future<T> &value) { cout << value.get() << '\n'; } int main() { promise<int> p; future<int> ft = p.get_future(); thread th(ImprimeFuture<int>, ref(ft) ); p.set_value(5); th.join(); return 0; } Exemplo 2: #include <iostream> #include <thread> #include <future> using namespace std;template<typename T> void ImprimeFuture(future<T> &value) { try { cout << value.get() << '\n'; } catch( int e ) { cout << "codigo do erro: " << e << endl; } } int main() { promise<int> p; future<int> ft = p.get_future(); thread th(ImprimeFuture<int>, ref(ft) ); try { throw 5; } catch(...) { p.set_exception( current_exception() ); } th.join(); return 0; } Exemplo 3: #include <iostream> #include <future> #include <thread> using namespace std; int tarefa07() { return 7; } int tarefa08() { return 8; } int tarefa09(promise<int>& p) { p.set_value(9);//Estamos passando o valor 9 para o future, ou seja, quando fizermos f3.get(), o get ira //nos retornar o 9 e não 10. return 10; } int main() { //Cria um packaged_task para a tarefa01() packaged_task<int()> task(tarefa07); future<int> f1 = task.get_future(); //Pega o future do packaged_task task thread(move(task)).detach(); //Dispara o thread //future future<int> f2 = async(launch::async, tarefa08); //future com promise promise<int> p; future<int> f3 = p.get_future(); //Passamos o future do promise para future f3 thread(tarefa09 ,ref(p)).detach(); cout << "Aguardando...\n"; //Aguarda até que o resultado esteja pronto f1.wait(); f2.wait(); f3.wait(); cout << "Pronto!\nOs resultados sao: " << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n'; } Neste exemplo, o thread que contém o objeto std::promise<> e que deveria gerar dados para o std::future<> gerou uma exceção que foi capturada e passada para o std::future<> através do método set_exception(). packaged_task Um objeto packaged_task<> armazena um callable object (função, functor ou lambda) que será executado posteriormente. É muito semelhante a um objeto std::function. A diferença é que um objeto packaged_task<>, transfere o resultado da chamada ao callable object para um objeto std::future<> que pode ser usado por exemplo, por um outro thread. #include <future> #include <iostream> #include <thread> using namespace std; double soma(double a, double b) { return a + b; } void imprimesoma(future<double> &ft) { cout << ft.get() << '\n'; } int main() { packaged_task<double(double,double)> task(soma); future<double> fd = task.get_future(); thread th1(move(task),10.0,50.0); thread th2(imprimesoma,ref(fd)); th1.join(); th2.join(); return 0; } Exemplo 2: #include <iostream> #include <future> #include <thread> using namespace std; int tarefa07() { return 7; } int tarefa08() { return 8; } int tarefa09(promise<int>& p) { p.set_value(9);//Estamos passando o valor 9 para o future, ou seja, quando fizermos f3.get(), o get ira //nos retornar o 9 e não 10. return 10; } int main() { //Cria um packaged_task para a tarefa01() packaged_task<int()> task(tarefa07); future<int> f1 = task.get_future(); //Pega o future do packaged_task task thread(move(task)).detach(); //Dispara o thread //future future<int> f2 = async(launch::async, tarefa08); //future com promise promise<int> p; future<int> f3 = p.get_future(); //Passamos o future do promise para future f3 thread(tarefa09 ,ref(p)).detach(); cout << "Aguardando...\n"; //Aguarda até que o resultado esteja pronto f1.wait(); f2.wait(); f3.wait(); cout << "Pronto!\nOs resultados sao: " << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n'; } Call once call_once é um template de função que através de uma variável do tipo once_flag controla a execução de um callable object, permitindo a execução deste apenas uma vez. #include <iostream> #include <mutex> using namespace std; once_flag flag; void f() { cout << "funcao f()\n"; } int main() { call_once(flag, f); call_once(flag, f); call_once(flag, f); return 0; } TLS – Thread Local Storage O TLS permite que cada thread tenha sua própria cópia daquela variável. A variável declarada com thread_local, será criada quando o thread iniciar e será liberada quando o thread encerrar. #include <iostream> #include <thread> using namespace std; thread_local int _value; void imprime_ate_20() { for(; _value < 20; ++_value) { cout << "thread " << this_thread::get_id() << ": " << _value << '\n'; this_thread::yield(); } } int main() { _value = 5; thread th1(imprime_ate_20); _value = 2; thread th2(imprime_ate_20); th1.join(); th2.join(); return 0; } Unions As unions do C++11 se tornaram menos restritivas em relação ao padrão C++03. Mas algumas restrições permanecem: Não podem ser usadas em herança, nem como base e nem como derivada, não podem ter funções virtuais, não podem ter referências como membro, não podem ter membros estáticos. O que mudou: Podem ter objetos como membro da union Podem ter funções especiais (construtoras, destrutora,...) Podem ter membros privados #include <iostream> using namespace std; union teste { int n; string s;//Apaga as destrutoras teste(){} ~teste(){} void alteraDouble(double y) { x = y; } double mostraDouble() { return x; } private: double x; }; int main() { teste t; t.n = 10; cout << t.n << "\n\n"; new (&t.s) string();//Placement new sobrecarga do operador new para instanciar minha string t.s = "Agit"; cout << t.s << "\n\n"; t.alteraDouble(99.8); cout << t.mostraDouble() << "\n\n"; return 0; } Chrono A biblioteca chrono é utilizada para o controle do tempo. Todos os recursos desta biblioteca estão disponíveis através do namespace std::chrono. Chrono trabalha com 3 (três) conceitos principais: - Durações: medem um tempo que passou, por exemplo: 10 segundos, 2 horas,etc. As durações são representadas por um objeto duration<> que se baseia em um contador e uma precisão do período, por exemplo, 10 é o contador e a precisão é segundos. - Pontos no tempo (time points): é a representação de um momento específico no tempo, por exemplo a data de nascimento, a hora do próximo trem, etc. São representados pelo objeto time_point<> e usa uma duração relativa a uma época (que é um ponto fixo no tempo comum a todos os time_points que
Compartilhar