Não adora C ++ quando se trata da linha de perguntas "recursos ocultos de"? Achei melhor jogar fora lá. Quais são alguns dos recursos ocultos do C ++?
Não adora C ++ quando se trata da linha de perguntas "recursos ocultos de"? Achei melhor jogar fora lá. Quais são alguns dos recursos ocultos do C ++?
Respostas:
A maioria dos programadores C ++ está familiarizada com o operador ternário:
x = (y < 0) ? 10 : 20;
No entanto, eles não percebem que pode ser usado como um lvalue:
(a == 0 ? a : b) = 1;
que é uma abreviatura para
if (a == 0)
a = 1;
else
b = 1;
Use com cuidado :-)
(value ? function1 : function2)()
.
function1
e function2
forem convertidos implicitamente em ponteiros de função, e o resultado será implicitamente convertido de volta.
Você pode colocar URIs na origem C ++ sem erros. Por exemplo:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
, que C ++ tem). Qualquer coisa após duas barras é um comentário. Portanto, com http://stackoverflow.com
, http
é um rótulo (você poderia teoricamente escrever goto http;
) e //stackoverflow.com
é apenas um comentário de fim de linha. Ambos são C ++ legais, então a construção compila. Não faz nada vagamente útil, é claro.
goto http;
, na verdade, não segue o URL. :(
Aritmética de ponteiro.
Os programadores C ++ preferem evitar ponteiros por causa dos bugs que podem ser introduzidos.
O C ++ mais legal que eu já vi? Literais analógicos.
Eu concordo com a maioria das postagens: C ++ é uma linguagem multiparadigma, então os recursos "ocultos" que você encontrará (além dos "comportamentos indefinidos" que você deve evitar a todo custo) são usos inteligentes de recursos.
A maioria dessas facilidades não são recursos integrados da linguagem, mas sim baseados em bibliotecas.
O mais importante é o RAII , muitas vezes ignorado por anos pelos desenvolvedores C ++ vindos do mundo C. Sobrecarga do operador é frequentemente um recurso mal compreendido que permite comportamento semelhante a array (operador subscrito), operações semelhantes a ponteiros (ponteiros inteligentes) e operações semelhantes a embutidas (multiplicação de matrizes.
O uso de exceção é muitas vezes difícil, mas com algum trabalho, pode produzir código realmente robusto por meio de segurança de exceção especificações de (incluindo código que não falhará, ou que terá recursos semelhantes a um commit que terá sucesso ou reverterá para seu estado original).
O recurso "oculto" mais famoso do C ++ é a metaprogramação de template , uma vez que permite que você tenha seu programa parcialmente (ou totalmente) executado em tempo de compilação em vez de em tempo de execução. No entanto, isso é difícil e você deve ter um domínio sólido sobre os modelos antes de tentar.
Outros fazem uso do paradigma múltiplo para produzir "formas de programação" fora do ancestral C ++, ou seja, C.
Usando functores , você pode simular funções, com a segurança de tipo adicional e com estado. Usando o padrão de comando , você pode atrasar a execução do código. A maioria dos outros padrões de design podem ser implementados de maneira fácil e eficiente em C ++ para produzir estilos de codificação alternativos que não deveriam estar na lista de "paradigmas oficiais de C ++".
Usando modelos , você pode produzir código que funcionará na maioria dos tipos, incluindo aquele que você não pensou a princípio. Você também pode aumentar a segurança de tipo (como um malloc / realloc / free typesafe automatizado). Os recursos do objeto C ++ são realmente poderosos (e, portanto, perigosos se usados descuidadamente), mas mesmo o polimorfismo dinâmico tem sua versão estática em C ++: o CRTP .
Eu descobri que a maioria dos livros do tipo " C ++ Eficaz " de Scott Meyers ou livros do tipo " C ++ Excepcional " de Herb Sutter são fáceis de ler e um tesouro de informações sobre recursos conhecidos e menos conhecidos do C ++.
Entre as minhas preferidas está uma que deve fazer o cabelo de qualquer programador Java erguer-se do horror: Em C ++, a maneira mais orientada a objetos de adicionar um recurso a um objeto é por meio de uma função não amiga de não membro, em vez de um membro função (ou seja, método de classe), porque:
Em C ++, a interface de uma classe é tanto suas funções-membro quanto as funções não-membro no mesmo namespace
funções não-membro não-amigáveis não têm acesso privilegiado ao interno da classe. Como tal, usar uma função de membro em vez de uma função não amiga não membro enfraquecerá o encapsulamento da classe.
Isso nunca deixa de surpreender, mesmo os desenvolvedores experientes.
(Fonte: Entre outros, o Guru on-line da semana de Herb Sutter nº 84: http://www.gotw.ca/gotw/084.htm )
Uma característica da linguagem que considero estar um tanto oculta, porque nunca tinha ouvido falar dela em todo o meu tempo na escola, é o alias de namespace. Não fui trazido à minha atenção até que encontrei exemplos disso na documentação do boost. Claro, agora que eu sei sobre isso, você pode encontrá-lo em qualquer referência C ++ padrão.
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
using
.
Não apenas as variáveis podem ser declaradas na parte inicial de um for
loop, mas também classes e funções.
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
Isso permite várias variáveis de tipos diferentes.
O operador de matriz é associativo.
A [8] é sinônimo de * (A + 8). Como a adição é associativa, isso pode ser reescrito como * (8 + A), que é um sinônimo de ..... 8 [A]
Você não disse útil ... :-)
A
não importa. Por exemplo, se A
fosse um char*
, o código ainda seria válido.
Algo que é pouco conhecido é que os sindicatos também podem ser modelos:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
E eles também podem ter construtores e funções-membro. Nada que tenha a ver com herança (incluindo funções virtuais).
From
e To
for definido e usado de acordo. No entanto, tal união pode ser usada com comportamento definido ( To
sendo uma matriz de caracteres não assinados ou uma estrutura compartilhando uma sequência inicial comFrom
). Mesmo se você usá-lo de forma indefinida, ainda pode ser útil para trabalhos de baixo nível. De qualquer forma, este é apenas um exemplo de um modelo de união - pode haver outros usos para um modelo de união.
C ++ é um padrão, não deve haver nenhum recurso oculto ...
C ++ é uma linguagem multiparadigma, você pode apostar seu último dinheiro na existência de recursos ocultos. Um exemplo entre muitos: metaprogramação de template . Ninguém no comitê de padrões pretendia que houvesse uma sublinguagem completa de Turing que fosse executada em tempo de compilação.
Outro recurso oculto que não funciona em C é a funcionalidade do unário +
operador . Você pode usá-lo para promover e degradar todos os tipos de coisas
+AnEnumeratorValue
E seu valor de enumerador, que antes tinha seu tipo de enumeração, agora tem o tipo inteiro perfeito que pode caber em seu valor. Manualmente, você dificilmente conheceria esse tipo! Isso é necessário, por exemplo, quando você deseja implementar um operador sobrecarregado para sua enumeração.
Você tem que usar uma classe que usa um inicializador estático da classe sem uma definição fora da classe, mas às vezes ele falha ao vincular? O operador pode ajudar a criar um temporário sem fazer suposições ou dependências de seu tipo
struct Foo {
static int const value = 42;
};
// This does something interesting...
template<typename T>
void f(T const&);
int main() {
// fails to link - tries to get the address of "Foo::value"!
f(Foo::value);
// works - pass a temporary value
f(+Foo::value);
}
Você quer passar dois ponteiros para uma função, mas simplesmente não funciona? A operadora pode ajudar
// This does something interesting...
template<typename T>
void f(T const& a, T const& b);
int main() {
int a[2];
int b[3];
f(a, b); // won't work! different values for "T"!
f(+a, +b); // works! T is "int*" both time
}
O tempo de vida de temporários limitados a referências constantes é algo que poucas pessoas conhecem. Ou pelo menos é meu conhecimento favorito de C ++ que a maioria das pessoas não conhece.
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
Um bom recurso que não é usado com frequência é o bloco try-catch de toda a função:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
O uso principal seria traduzir a exceção para outra classe de exceção e relançar, ou traduzir entre exceções e tratamento de código de erro baseado em retorno.
return
pegar o bloco de função Try, apenas relançar.
Muitos sabem da identity
/ id
metafunção, mas há um bom caso de uso para casos não-template: Facilidade de escrita de declarações:
// void (*f)(); // same
id<void()>::type *f;
// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);
// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];
// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;
Isso ajuda muito a descriptografar declarações C ++!
// boost::identity is pretty much the same
template<typename T>
struct id { typedef T type; };
template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;
-> pointer<function<void,int>> f(pointer<function<void,void>>);
ou pointer<void(int)> f(pointer<void()>);
oufunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
Um recurso bastante oculto é que você pode definir variáveis dentro de uma condição if, e seu escopo se estenderá apenas sobre os blocos if e else:
if(int * p = getPointer()) {
// do something
}
Algumas macros usam isso, por exemplo, para fornecer algum escopo "bloqueado" como este:
struct MutexLocker {
MutexLocker(Mutex&);
~MutexLocker();
operator bool() const { return false; }
private:
Mutex &m;
};
#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else
void someCriticalPath() {
locked(myLocker) { /* ... */ }
}
Além disso, BOOST_FOREACH o usa sob o capô. Para completar isso, não é apenas possível em um if, mas também em um switch:
switch(int value = getIt()) {
// ...
}
e em um loop while:
while(SomeThing t = getSomeThing()) {
// ...
}
(e também em uma condição para). Mas não tenho certeza se eles são tão úteis :)
if((a = f()) == b) ...
, mas essa resposta realmente declara uma variável na condição.
for(...; int i = foo(); ) ...;
isto irá percorrer o corpo, desde que i
seja verdadeiro, inicializando-o a cada vez. O loop que você mostra está simplesmente demonstrando uma declaração de variável, mas não uma declaração de variável que atua simultaneamente como uma condição :)
Às vezes, você faz uso válido do operador vírgula, mas deseja garantir que nenhum operador vírgula definido pelo usuário atrapalhe, porque, por exemplo, você confia em pontos de sequência entre os lados esquerdo e direito ou deseja ter certeza de que nada interfere com o desejado açao. É aqui que void()
entra o jogo:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
Ignore os espaços reservados que coloquei para a condição e o código. O que é importante é o void()
, o que força o compilador a usar o operador vírgula embutido. Isso pode ser útil ao implementar classes de características, às vezes, também.
Inicialização de array no construtor. Por exemplo, em uma classe, se tivermos uma matriz de int
como:
class clName
{
clName();
int a[10];
};
Podemos inicializar todos os elementos da matriz para seu padrão (aqui, todos os elementos da matriz para zero) no construtor como:
clName::clName() : a()
{
}
Oooh, em vez disso, posso fazer uma lista de ódios de animais de estimação:
Do lado positivo
Você pode acessar dados protegidos e membros de função de qualquer classe, sem comportamento indefinido e com semântica esperada. Continue lendo para ver como. Leia também o relatório de defeito sobre isso.
Normalmente, C ++ proíbe que você acesse membros protegidos não estáticos de um objeto de classe, mesmo se essa classe for sua classe base
struct A {
protected:
int a;
};
struct B : A {
// error: can't access protected member
static int get(A &x) { return x.a; }
};
struct C : A { };
Isso é proibido: você e o compilador não sabem para onde a referência realmente aponta. Pode ser um C
objeto, caso em que a classe B
não tem negócios e não tem nenhuma pista sobre seus dados. Esse acesso só é concedido se x
for uma referência a uma classe derivada ou derivada dela. E poderia permitir que um pedaço de código arbitrário leia qualquer membro protegido simplesmente criando uma classe "descartável" que lê membros, por exemplo std::stack
:
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
// error: stack<int>::c is protected
return s.c;
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Certamente, como você vê, isso causaria muitos danos. Mas agora, os ponteiros de membro permitem contornar essa proteção! O ponto principal é que o tipo de um ponteiro de membro está vinculado à classe que realmente contém o referido membro - não à classe que você especificou ao obter o endereço. Isso nos permite contornar a verificação
struct A {
protected:
int a;
};
struct B : A {
// valid: *can* access protected member
static int get(A &x) { return x.*(&B::a); }
};
struct C : A { };
E, claro, também funciona com o std::stack
exemplo.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
return s.*(pillager::c);
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
Isso será ainda mais fácil com uma declaração de uso na classe derivada, que torna o nome do membro público e se refere ao membro da classe base.
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
using std::stack<int>::c;
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = s.*(&pillager::c);
}
Outro recurso oculto é que você pode chamar objetos de classe que podem ser convertidos em ponteiros de função ou referências. A resolução da sobrecarga é feita no resultado deles, e os argumentos são perfeitamente encaminhados.
template<typename Func1, typename Func2>
class callable {
Func1 *m_f1;
Func2 *m_f2;
public:
callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
operator Func1*() { return m_f1; }
operator Func2*() { return m_f2; }
};
void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }
int main() {
callable<void(int), void(long)> c(foo, bar);
c(42); // calls foo
c(42L); // calls bar
}
Elas são chamadas de "funções de chamada substituta".
Recursos ocultos:
Se uma função lançar uma exceção não listada em suas especificações de exceção, mas a função lançar std::bad_exception
em sua especificação de exceção, a exceção é convertida em std::bad_exception
e lançada automaticamente. Dessa forma, você saberá pelo menos que um bad_exception
foi lançado. Leia mais aqui .
blocos de teste de função
A palavra-chave template na eliminação da ambiguidade de typedefs em um template de classe. Se o nome de um modelo de especialização membro aparece depois de um .
, ->
ou ::
operador, e que tem o nome parâmetros do modelo explicitamente qualificados, o prefixo do nome do modelo de membro com o modelo de palavra-chave. Leia mais aqui .
os padrões dos parâmetros da função podem ser alterados em tempo de execução. Leia mais aqui .
A[i]
funciona tão bem quanto i[A]
Instâncias temporárias de uma classe podem ser modificadas! Uma função de membro não const pode ser chamada em um objeto temporário. Por exemplo:
struct Bar {
void modify() {}
}
int main (void) {
Bar().modify(); /* non-const function invoked on a temporary. */
}
Leia mais aqui .
Se dois tipos diferentes estiverem presentes antes e depois de :
in na ?:
expressão do operador ternário ( ), o tipo resultante da expressão será o mais geral dos dois. Por exemplo:
void foo (int) {}
void foo (double) {}
struct X {
X (double d = 0.0) {}
};
void foo (X) {}
int main(void) {
int i = 1;
foo(i ? 0 : 0.0); // calls foo(double)
X x;
foo(i ? 0.0 : x); // calls foo(X)
}
map::operator[]
cria a entrada se a chave estiver faltando e retorna a referência ao valor da entrada construída por padrão. Então você pode escrever:
map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
s.assign(...);
}
cout << s;
Estou surpreso com a quantidade de programadores C ++ que não sabem disso.
.find()
.
const map::operator[]
gera mensagens de erro"
Colocar funções ou variáveis em um namespace sem nome torna obsoleto o uso de static
para restringi-los ao escopo do arquivo.
static
em escopo global não está obsoleto de forma alguma. (Para referência: C ++ 03 §D.2)
static
use só deve ser usado em um tipo de classe ou função.
Definir funções de amigo comuns em modelos de classe requer atenção especial:
template <typename T>
class Creator {
friend void appear() { // a new function ::appear(), but it doesn't
… // exist until Creator is instantiated
}
};
Creator<void> miracle; // ::appear() is created at this point
Creator<double> oops; // ERROR: ::appear() is created a second time!
Neste exemplo, duas instanciações diferentes criam duas definições idênticas - uma violação direta do ODR
Devemos, portanto, ter certeza de que os parâmetros do modelo do modelo de classe aparecem no tipo de qualquer função amiga definida naquele modelo (a menos que desejemos evitar mais de uma instanciação de um modelo de classe em um arquivo específico, mas isso é bastante improvável). Vamos aplicar isso a uma variação do nosso exemplo anterior:
template <typename T>
class Creator {
friend void feed(Creator<T>*){ // every T generates a different
… // function ::feed()
}
};
Creator<void> one; // generates ::feed(Creator<void>*)
Creator<double> two; // generates ::feed(Creator<double>*)
Isenção de responsabilidade: colei esta seção de Modelos C ++: O Guia Completo / Seção 8.4
Pouco conhecido, mas o código a seguir está bom
void f() { }
void g() { return f(); }
Bem como a seguinte aparência estranha
void f() { return (void)"i'm discarded"; }
Sabendo disso, você pode tirar vantagem em algumas áreas. Um exemplo: as void
funções não podem retornar um valor, mas você também não pode simplesmente retornar nada, porque elas podem ser instanciadas com non-void. Em vez de armazenar o valor em uma variável local, o que causará um erro para void
, basta retornar um valor diretamente
template<typename T>
struct sample {
// assume f<T> may return void
T dosomething() { return f<T>(); }
// better than T t = f<T>(); /* ... */ return t; !
};
Leia um arquivo em um vetor de strings:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
- faltando parênteses após o segundo parâmetro
Você pode modelar campos de bits.
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
Eu ainda não descobri nenhum propósito para isso, mas com certeza me surpreendeu.
Uma das gramáticas mais interessantes de qualquer linguagem de programação.
Três dessas coisas pertencem uma à outra, e duas são algo totalmente diferente ...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
Todos, exceto o terceiro e o quinto, definem um SomeType
objeto na pilha e o inicializam (com u
nos dois primeiros casos, e o construtor padrão no quarto. O terceiro está declarando uma função que não leva parâmetros e retorna um SomeType
. O quinto está declarando de forma semelhante uma função que leva um parâmetro por valor do tipo SomeType
nomeado u
.
Livrar-se de declarações futuras:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
Escrevendo instruções switch com operadores?::
string result =
a==0 ? "zero" :
a==1 ? "one" :
a==2 ? "two" :
0;
Fazer tudo em uma única linha:
void a();
int b();
float c = (a(),b(),1.0f);
Zerando structs sem memset:
FStruct s = {0};
Normalizando / envolvendo valores de ângulo e tempo:
int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150
Atribuição de referências:
struct ref
{
int& r;
ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
FStruct s = {};
é ainda mais curto.
main
? Eu sugeriria global().main();
e simplesmente esqueceria o singleton ( você pode apenas trabalhar com o temporário, que prolonga sua vida útil )
O operador condicional ternário ?:
requer que seu segundo e terceiro operando tenham tipos "agradáveis" (falando informalmente). Mas esse requisito tem uma exceção (trocadilho intencional): o segundo ou o terceiro operando pode ser uma expressão de lançamento (que tem tipo void
), independentemente do tipo do outro operando.
Em outras palavras, pode-se escrever as seguintes expressões C ++ perfeitamente válidas usando o ?:
operador
i = a > b ? a : throw something();
BTW, o fato de que throw expression é na verdade uma expressão (do tipo void
) e não uma instrução é outro recurso pouco conhecido da linguagem C ++. Isso significa, entre outras coisas, que o código a seguir é perfeitamente válido
void foo()
{
return throw something();
}
embora não haja muito sentido em fazer dessa maneira (talvez em algum código de modelo genérico isso possa ser útil).
A regra de dominância é útil, mas pouco conhecida. Ele diz que mesmo se em um caminho não exclusivo através de uma estrutura de classe base, a pesquisa de nome para um membro parcialmente oculto é única se o membro pertencer a uma classe base virtual:
struct A { void f() { } };
struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };
// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };
Usei isso para implementar o suporte de alinhamento que descobre automaticamente o alinhamento mais estrito por meio da regra de dominância.
Isso não se aplica apenas a funções virtuais, mas também a nomes de typedef, membros estáticos / não virtuais e qualquer outra coisa. Já vi isso ser usado para implementar características substituíveis em metaprogramas.
struct C
em seu exemplo ...? Felicidades.