Posts Tagueados ‘tips’
C++ Idioms: SFINAE
Introdução
Nesse post falarei sobre uma técnica (ou recurso) utilizada em C++ que estou utilizando muito no meu projeto[0][1] na Boost Libraries[2] no GSoC, essa técnica é o SFINAE.
SFINAE[3] é uma sigla para “Substitution failure is not an error” que em uma tradução livre quer dizer “Falha na substituição não é um erro”. Segundo[3]:
SFINAE se refere a uma situação em C++ que uma substituição inválida de templates não é um erro.
Especificamente, na criação de um conjunto candidato para a resolução de sobrecarga, alguns (talvez todos) os candidatos deste conjunto pode ser resultante da substituição de argumentos templates deduzidos para os parâmetros de template. Se um erro ocorre durante a substituição, o compilador remove essa potencial sobrecarga do conjunto candidato ao invés de parar com um erro de compilação. Se restam um ou mais candidatos (i.é: pelo menos uma sobrecarga é válida), a resolução é feita e a invocação é bem formada.
Como funciona
Para entender melhor vejamos esse trecho de código:
template <typename T>
struct A
{
typedef T type;
T value;
};
template <>
struct A<bool>
{
};
template<typename T>
typename A<T>::type func(const A<T>& param)
{
std::cout << param.value << std::endl;
}
Esse código compila sem warnings. A função template func() é sobrecarregada e retorna o tipo T. Mas você deve estar se perguntando, o que acontece quando T = bool? Esse é justo o ponto do SFINAE!
Para T = bool, typename A::type seria uma declaração inválida já que A não contém o tipo type declarado, porém, com SFINAE, essa sobrecarga é simplesmente descartada e para qualquer outro T que não bool, func(A) é uma chamada legal porque sua sobrecarga é gerada.
Aplicações
Como eu disse no começo deste post eu estou utilizando bastante SFINAE no meu projeto do Google Summer of Code com uma utilidade chamada enable_if[4] encontrado na Boost.
Enable_if é uma classe (ou struct) que “ativa” a declaração de algo (classe, função, etc) se uma condição for satisfeita por meio do SFINAE. Vou mostrar aqui uma maneira de fazer um enable_if like, e quem quiser saber mais sobre o enable_if da Boost é encorajado a ler[4][5].
Vamos ao código:
/* Cond é a condição de ativação
* Type é o tipo que será declarado pelo membro type.
*/
template <bool Cond, typename Type = void>
struct enable_if_c
{
typedef Type type;
};
/*
* Especialização, quando Cond for false,
* esse template vazio é invocado.
*/
template <typename Type>
struct enable_if_c<false, Type>
{
// empty
};
Ou seja, sempre quando Cond for true, existirá um membro type declarado em enable_if_c, quando Cond for false o membro type não existirá.
Um exemplo de uso:
/*
* Nossa função pega o bit mais significativo de value e retorna um bool.
* ***Note que nesse exemplo estamos assumindo que T é um tipo inteiro.***
*/
/*
* Para retornar o bit mais significativo de um T com 4bytes (32 bits),
* deslocamos 31 bits para a direita e fazemos um & com 1.
*/
template <typename T>
typename enable_if_c<sizeof(T) == 4, bool>::type msb(T value)
{
std::cout << "Value have 4 bytes.\n";
return (value >> 31) & T(1);
}
/*
* Porém se T é um tipo de 1byte (8 bits) o deslocamento deve ser de
* 7 bits para pegarmos o bit mais significativo.
*/
template <typename T>
typename enable_if_c<sizeof(T) == 1, bool>::type msb(T value)
{
std::cout << "Value have 1 byte.\n";
return (value >> 7) & T(1);
}
Ou seja, a função de cima, é sobrecarregada para todos os tipos que tenham 32 bits (sizeof(T) == 4) e a de baixo é sobrecarregada para todos os tipos que tenham 8 bits (sizeof(T) == 1). Assim, no processo de resolução de sobrecarga, os outros tipos que não tem nem 8 nem 32 bits, são descartadas pois cairá na especialização enable_if_c que não possui um membro type (usado como retorno da função msb), comportamento citado anteriormente.
Para testar:
int main()
{
int a = -3;
unsigned b = 190000;
char c = 129;
uint16_t d = 10;
uint64_t e = 10000000;
std::cout << "Retorno int: " << msb(a) << std::endl;
std::cout << "Retorno unsigned: " << msb(b) << std::endl;
std::cout << "Retorno char: " << msb(c) << std::endl;
}
Se tentarmos executar com uma condição que não é true, teremos um erro em tempo de compilação, como podemos ver abaixo:
... uint16_t d = 10; uint64_t e = 10000000; std::cout << "Ret: uint16_t: " << msb(d) << std::endl std::cout << "Ret: uint64_t: " << msb(e) << std::endl; ... /* /Users/murilo/Documents/programming/blog/sfinae.cpp: In function ‘int main()’: /Users/murilo/Documents/programming/blog/sfinae.cpp:61: error: no matching function for call to ‘msb(uint16_t&)’ /Users/murilo/Documents/programming/blog/sfinae.cpp:62: error: no matching function for call to ‘msb(uint64_t&)’ */
Alguns outros exemplos usando o enable_if da Boost você pode encontrar no meu projeto[1] na Boost onde utilizo enable_if aos montes.
Nesse post exemplifiquei SFINAE com funções, mas também é aplicável para classes. Um exemplo você pode ver aqui [6].
Até a próxima!
Referências
[0] – Estou no Google Summer of Code 2010 – http://murilo.wordpress.com/2010/05/03/estou-no-google-summer-of-code-2010
[1] – Source of Bits and Ints project – https://svn.boost.org/trac/boost/browser/sandbox/SOC/2010/bits_and_ints/boost/integer
[2] – Boost C++ Libraries – http://www.boost.org/
[3] – Artigo “Substitution failure is not an error” na Wikipedia – http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error
[4] – Boost enable_if – http://www.boost.org/doc/libs/1_43_0/libs/utility/enable_if.html
[5] – enable_if.hpp – http://www.boost.org/doc/libs/1_35_0/boost/utility/enable_if.hpp
[6] – static_sign_extend.hpp at Boost Trac – https://svn.boost.org/trac/boost/browser/sandbox/SOC/2010/bits_and_ints/boost/integer/static_sign_extend.hpp
Como programar em C Orientado a Objetos
Olá, hoje eu vou dar um tempo na série “Coisas simples de se fazer em C++ que alguns ainda complicam” e irei falar sobre uma experiência minha tentando alguma maneira de programar em C orientado a objetos.
Sei que muitos vão pensar “pra quê isso?” ou dizer que é péssimo fazer isso ou coisa parecida, mas o intuito desse post é outro: é mostrar o que é possível fazer ou até mesmo enxergar alguma utilidade nisso. Nesse blog, procuro colocar coisas diferentes, curiosidades sobre programação e linguagens, porque acho que conteúdo “normal” já existem em muitos lugares na grande rede.
O que me instigou a fazer isso foi um post no CODARE “C: Escondendo o conteúdo de structs com tipos incompletos” de autoria do Thiago Santos, no qual ele cita sobre usar com orientação a objetos em C.
Li, achei interessante e quis, vamos dizer assim, dar um cara mais parecida de orientação a objetos ao que ele fez.
O primeiro passo foi pensar em como funcionam os famosos objetos.
Uma classe em uma linguagem orientada a objetos geralmente tem 2 tipos de elementos:
- Elementos de classe: elementos (funções e variáveis estáticas) que só são criados uma vez, todos os objetos da classe tem acesso ao mesmo elemento.
- Elementos de instância (ou objeto): elementos (atributos) que são criadas para cada objeto instanciado.
Ou seja:
class Animal
{
int age;
std::string specie;
static int count;
public:
int birthday();
};
No exemplo acima, age e specie são elementos de instância, ou seja, cada objeto têm o seus próprios. Já count e birthday() são de classe, pois todos os objetos dessa classe utilizarão o mesmo. O que ocorre no caso de funções é que na chamada da função, um ponteiro do objeto chamador é passado para a função. Isso faz com que não precise a cada instância de objeto criar uma nova função já que elas fazem a mesma coisa.
Algo como:
Animal animal, animal2; //lembrando que é APENAS uma ilustração do que acontece //não é assim que é realmente implementado mas é a mesma idéia //uma função só para todos os objetos de uma classe animal.birthday(); //vtable::animal::birthday(&animal); animal2.birthday(); //vtable::animal::birthday(&animal2);
A função birthday() é a mesma para as duas chamadas, o que muda é o ponteiro para os dados de cada objeto.
A minha implementação seguiu a idéia do Thiago Santos: uma classe person que tem os atributos name e age funções para instanciar, imprimir o nome e idade e deletar o objeto.
Vamos então dar uma olhada no nosso headerperson.h:
#ifndef PERSON_H__
#define PERSON_H__
//Incomplete type declaration
typedef struct person_private person_private;
typedef struct person {
//"private" data.
person_private* data;
//"class" functions
void (*free)();
void (*print)();
} person;
//instatiate a new person
person* new_person(const char*, int);
//pointer to the actual person in the context
person* __actual_person;
//sets the actual person
person* _(person* obj);
#endif
Nosso header contém a declaração incompleta de person_private leia aqui para saber o porquê, a definição da nossa “classe” person, as declarações das funções new_person() e _() e um ponteiro __actual_person.
Nossa “classe” person contém um ponteiro data para os dados que não poderão ser acessíveis através do objeto (name, age) além de ponteiros para funções (que serão nossos “métodos”).
A função new_person simplesmente instancia um objeto do tipo person.
O ponteiro __actual_person irá funcionar como o ponteiro passado para as funções de classe. Através do ponteiro saberemos qual objeto chamou a função.
A função _() seta o ponteiro __actual_person para que as funções sejam corretamente chamadas. Nota: não é thread safe. (hehehe)
Vamos ao nosso person.c:
#include "person.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//private data... visible only by the functions below
struct person_private
{
char* name;
int age;
};
//a "manual destructor"
void free_person()
{
free(__actual_person->data->name);
free(__actual_person->data);
free(__actual_person);
puts("Person sucessfuly freed!\nBye");
}
//prints
void print_person()
{
printf("%s -: %d\n", __actual_person->data->name,
__actual_person->data->age);
}
person* new_person(const char* name, int age)
{
//Allocate the object
person* new = (person*)malloc(sizeof(person));
new->data = (person_private*)malloc(sizeof(person_private));
//Initialize the data
new->data->name = (char*)malloc(strlen(name) * sizeof(char) + 1);
strcpy(new->data->name, name);
new->data->age = age;
//Set the functions pointers
new->print = print_person;
new->free = free_person;
return new;
}
//must call the objects with this function
person* _(person* obj)
{
__actual_person = obj;
return obj;
}
O que temos aí é a função _() que seta o objeto atual (__actual_person) para que possa ser utilizado a função com o objeto certo, a função new_person que funciona como nosso construtor, as outras duas funções que são os “métodos da classe” print e free e a definição da estrutura person_private que contém os atributos da classe.
Os comentários ajudam no resto
.
Vamos então ver o main.c que é o nosso teste:
#include <stdio.h>
#include "person.h"
int main()
{
//Instantiates 2 persons
person* person1 = new_person("Murilo", 21);
person* person2 = new_person("Rovane", 47);
//print
_(person1)->print();
_(person2)->print();
//free
_(person1)->free();
_(person2)->free();
}
Eis o resultado:
murilo@blacksheep:~/programacao/cobject$ gcc main.c person.c
murilo@blacksheep:~/programacao/cobject$ ./a.out
Murilo -: 21
Rovane -: 47
Person sucessfuly freed!
Bye
Person sucessfuly freed!
Bye
Nota: deve-se usar sempre a função _() para utilizar o objeto.
Fiquei pensando se tem como fazer uma espécie de gerador de classes nesse formato no próprio C (com macros por exemplo). Vou procurar saber se existe algo a respeito, são 04h40min da mardugada não estou mais com boas idéias.
Qualquer sugestão, bug, idéia, reclamação, estamos aí!
Sobrecarga do operator++ para enums
Mais uma trick com enums.
A sobrecarga de operadores podem facilitar o manuseio de enums. Abaixo está um exemplo de como pode ser útil a sobrecarga do operator++.
#include <iostream>
using std::cout;
using std::endl;
enum Semana
{
DOM,
SEG,
TER,
QUA,
QUI,
SEX,
SAB
};
Semana& operator++(Semana& dia) //pré-incremento
{
return dia = static_cast<Semana>((dia + 1) % 7);
}
Semana& operator++(Semana& dia, int) //pós-incremento
{
return dia = static_cast<Semana>((dia + 1) % 7);
}
int main()
{
Semana dia = SEX;
cout << dia << endl; // SEX (5)
cout << ++dia << endl; // SAB (6)
cout << ++dia << endl; // DOM (0)
dia++; // SEG (1)
cout << dia << endl;
}
Quando chegamos no último dia da semana (SAB) e chamamos o operator++, obtemos o primeiro dia da semana graças ao operador módulo (%).