Recursos ocultos do C ++? [fechadas]


114

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 ++?


@Devtron - Eu vi alguns bugs incríveis (isto é, comportamento inesperado) vendidos como recursos. Na verdade, a indústria de jogos realmente tenta fazer isso acontecer hoje em dia e chama de "gameplay emergente" (também, dê uma olhada em "TK Surfing" do Psi-Ops, era puramente um bug, então eles deixaram como está e é um dos melhores características do jogo IMHO)
Grant Peters

5
@Laith J: Poucas pessoas leram o padrão ISO C ++ de 786 páginas de capa a capa - mas suponho que você leu, e manteve tudo isso, certo?
j_random_hacker

2
@Laith, @j_random: Veja minha pergunta "O que é uma piada de programador, como posso reconhecê-la e qual é a resposta apropriada" em stackoverflow.com/questions/1/you-have-been-link-rolled .

Respostas:


308

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 :-)


11
Muito interessante. Eu posso ver isso fazendo algum código ilegível embora.
Jason Baker de

112
Caramba. (a == 0? a: b) = (y <0? 10:20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
Não sei se é específica GCC, mas fiquei surpreso de encontrar este também trabalhou: (value ? function1 : function2)().
Chris Burt-Brown,

3
@Chris Burt-Brown: Não, isso deve funcionar em qualquer lugar se eles tiverem o mesmo tipo (ou seja, sem argumentos padrão) function1 e function2forem convertidos implicitamente em ponteiros de função, e o resultado será implicitamente convertido de volta.
MSalters

238

Você pode colocar URIs na origem C ++ sem erros. Por exemplo:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Mas apenas um por função, eu suspeito? :)
Constantin

51
@jpoh: http seguido de dois-pontos se torna um "rótulo" que você usará em uma instrução goto posteriormente. você recebe esse aviso do seu compilador porque ele não é usado em nenhuma instrução goto no exemplo acima.
utku_karatas

9
Você pode adicionar mais de um, desde que tenham protocolos diferentes! ftp.microsoft.com gopher: //aerv.nl/1 e assim por diante ...
Daniel Earwicker

4
@Pavel: Um identificador seguido por dois pontos é um rótulo (para uso com o 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.
David Thornley

8
Infelizmente goto http;, na verdade, não segue o URL. :(
Yakov Galka

140

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.


11
Evitamos ponteiros por causa de bugs? Os ponteiros são basicamente tudo o que a codificação C ++ dinâmica significa!
Nick Bedford

1
Literais analógicos são ótimos para inscrições ofuscadas em concursos C ++, especialmente o tipo ASCII-art.
Synetech

119

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 )


1 resposta muito completa. está incompleto por razões óbvias (caso contrário, não haveria mais "recursos ocultos"!): p no primeiro ponto no final da resposta, você mencionou membros de uma interface de classe. você quer dizer ".. são suas funções de membro e as funções de amigo não membro"?
wilhelmtell


o que você está mencionando com 1 must be koenig lookup, certo?
Özgür

1
@wilhelmtell: Não não não ... :-p ... quero dizer "suas funções-membro e funções não-membro NÃO-AMIGOS" .... A pesquisa de Koenig garantirá que essas funções sejam consideradas mais cedo do que outras " fora "funciona em sua busca por símbolos
paercebal

7
Excelente postagem e +1 especialmente para a última parte, que poucas pessoas percebem. Eu provavelmente adicionaria a biblioteca Boost também como um "recurso oculto". Eu praticamente a considero a biblioteca padrão que C ++ deveria ter. ;)
jalf

118

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 );

1
Acho que isso é útil se você não quiser usar using.
Siqi Lin de

4
Também é útil como uma maneira de alternar entre as implementações, seja selecionando, digamos, thread-safe versus non-thread-safe, ou versão 1 versus 2.
Tony Delroy

3
É especialmente útil se você estiver trabalhando em um projeto muito grande com grandes hierarquias de namespace e não quiser que seus cabeçalhos causem poluição de namespace (e você deseja que suas declarações de variáveis ​​sejam legíveis por humanos).
Brandon Bohrer,

102

Não apenas as variáveis ​​podem ser declaradas na parte inicial de um forloop, 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.


31
É bom saber que você pode fazer isso, mas pessoalmente, eu realmente tentaria evitar fazer algo assim. Principalmente porque é difícil de ler.
Zoomulator

2
Na verdade, o que funcionaria neste contexto é usar um par: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin bem, então eu recomendo que você tente fazer um relatório de bug contra o VS2008 ao invés de downvoting o recurso oculto. É claramente culpa do seu compilador.
Johannes Schaub - litb

2
Hmm, também não funciona no msvc10. Que triste :(
avakar

2
@avakar, na verdade, o gcc introduziu um bug que o torna rejeitado também, na v4.6 :) consulte gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb

77

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 ... :-)


15
Na verdade, ao usar este truque, você realmente deve prestar atenção ao tipo que está usando. A [8] é na verdade o 8º A enquanto 8 [A] é o inteiro Ath começando no endereço 8. Se A for um byte, você tem um bug.
Vincent Robert,

38
você quer dizer "comutativo" onde diz "associativo"?
DarenW

28
Vincent, você está errado. O tipo de Anão importa. Por exemplo, se Afosse um char*, o código ainda seria válido.
Konrad Rudolph,

11
Esteja ciente de que A deve ser um ponteiro, e não um operador de sobrecarga de classe [].
David Rodríguez - dribeas

15
Vincent, nisso deve haver um tipo integral e um tipo de ponteiro, e nem C nem C ++ se importam com qual deles vai primeiro.
David Thornley

73

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).


Interessante! Então, você deve inicializar todos os membros? Ele segue a ordem usual da estrutura, implicando que o último membro será inicializado "em cima" dos membros anteriores?
j_random_hacker

j_random_hacker oh, certo, isso é um absurdo. boa pegada. eu escrevi como seria uma estrutura. espere, vou consertar
Johannes Schaub - litb

Isso não invoca um comportamento indefinido?
Greg Bacon

7
@gbacon, sim, ele invoca o comportamento indefinido se Frome Tofor definido e usado de acordo. No entanto, tal união pode ser usada com comportamento definido ( Tosendo 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.
Johannes Schaub - litb

3
Cuidado com o construtor. Observe que você deve construir apenas o primeiro elemento, e isso só é permitido em C ++ 0x. A partir do padrão atual, você deve se ater a tipos trivialmente construtíveis. E sem destruidores.
Potatoswatter

72

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.


65

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

Converter uma enumeração em um inteiro

+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.

Obtenha o valor de uma variável

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);
}

Decompõe uma matriz em um ponteiro

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
}

61

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

3
Você pode elaborar? Como está, você está apenas brincando;)
Joseph Garvin

8
ScopeGuard ( ddj.com/cpp/184403758 ) é um ótimo exemplo que aproveita esse recurso.
MSN

2
Estou com Joseph Garvin. Por favor, nos esclareça.
Peter Mortensen

Eu apenas fiz nos comentários. Além disso, é uma consequência natural de usar um parâmetro de referência const.
MSN


52

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.


Eu não acho que você pode returnpegar o bloco de função Try, apenas relançar.
Constantin,

Acabei de tentar compilar o anterior e não deu nenhum aviso. Acho que o exemplo acima funciona.
vividos

7
o retorno só é proibido para construtores. O bloco try de função de um construtor detectará erros ao inicializar a base e os membros (o único caso em que um bloco try de função faz algo diferente do que apenas tentar dentro da função); não jogar novamente resultaria em um objeto incompleto.
puetzk

Sim. Isso é muito útil. Eu escrevi macros BEGIN_COM_METHOD e END_COM_METHOD para capturar exceções e retornar HRESULTS para que as exceções não vazassem de uma classe que implementa uma interface COM. Funcionou bem.
Scott Langham

3
Como apontado por @puetzk, esta é a única maneira de lidar com exceções lançadas por qualquer coisa na lista de inicializadores de um construtor , como construtores de classes base ou de membros de dados.
anton.burger

44

Muitos sabem da identity/ idmetafunçã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; };

Interessante, mas inicialmente tive mais problemas para ler algumas dessas definições. Outra maneira de corrigir o problema de dentro para fora com as declarações C ++ é escrever alguns aliases de tipo de modelo: 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;
bames53

42

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 :)


Arrumado! Eu nunca soube que você poderia fazer isso ... isso teria (e irá) economizar alguns problemas ao escrever código com valores de retorno de erro. Existe alguma maneira de ainda ter uma condicional em vez de apenas! = 0 neste formulário? if ((int r = func ()) <0) não parece funcionar ...
puetzk

puetzk, não, não há. mas que bom que você gostou :)
Johannes Schaub - litb

4
@Frerich, isso não é possível no código C. Acho que você está pensando if((a = f()) == b) ..., mas essa resposta realmente declara uma variável na condição.
Johannes Schaub - litb de

1
@Angry é muito diferente, porque a declaração da variável é testada para seu valor booleano imediatamente. Também há um mapeamento para loops for, que parece que for(...; int i = foo(); ) ...;isto irá percorrer o corpo, desde que iseja 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 :)
Johannes Schaub - litb

5
Muito bom, exceto que você não mencionou que o uso pretendido para esse recurso era para projeções dinâmicas de ponteiro, eu acredito.
mmocny

29

Evitando que o operador vírgula chame sobrecargas de operador

À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.


Eu só usei isso para acabar com minha ignorante expressão exagerada . :)
GManNickG

28

Inicialização de array no construtor. Por exemplo, em uma classe, se tivermos uma matriz de intcomo:

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()
{
}

6
Você pode fazer isso com qualquer array em qualquer lugar.
Potatoswatter

@Potatoswatter: mais difícil do que parece, devido à análise mais incômoda. Não consigo pensar em nenhum outro lugar em que isso possa ser feito, exceto talvez um valor de retorno
Mooing Duck

Se o tipo da matriz for um tipo de classe, isso não é necessário, certo?
Thomas Eding

27

Oooh, em vez disso, posso fazer uma lista de ódios de animais de estimação:

  • Os destruidores precisam ser virtuais se você pretende usar polimorficamente
  • Às vezes, os membros são inicializados por padrão, às vezes não são
  • Classes locais não podem ser usadas como parâmetros de modelo (os torna menos úteis)
  • especificadores de exceção: parecem úteis, mas não são
  • sobrecargas de função ocultam funções de classe base com assinaturas diferentes.
  • nenhuma padronização útil na internacionalização (conjunto de caracteres amplo padrão portátil, alguém? Teremos que esperar até C ++ 0x)

Do lado positivo

  • recurso oculto: blocos de teste de função. Infelizmente, não encontrei um uso para isso. Sim, eu sei por que eles adicionaram, mas você tem que relançar em um construtor que o torna inútil.
  • Vale a pena examinar cuidadosamente as garantias de STL sobre a validade do iterador após a modificação do contêiner, o que pode permitir que você faça alguns loops ligeiramente melhores.
  • Boost - dificilmente é um segredo, mas vale a pena usar.
  • Otimização do valor de retorno (não é óbvio, mas é especificamente permitido pelo padrão)
  • Functores também conhecidos como objetos de função, também conhecidos como operador (). Isso é usado extensivamente pelo STL. não é realmente um segredo, mas é um efeito colateral bacana de sobrecarga de operador e modelos.

16
Odeio de estimação: nenhuma ABI definida para aplicativos C ++, ao contrário dos aplicativos C que todos usam, porque cada linguagem pode garantir a chamada de uma função C, ninguém pode fazer o mesmo para C ++.
gbjbaanb

8
Os destruidores precisam ser virtuais apenas se você pretende destruir polimorficamente, o que é um pouco sutilmente diferente do primeiro ponto.
David Rodríguez - dribeas

2
Com C ++ 0x, tipos locais podem ser usados ​​como parâmetros de modelo.
tstenner

1
Com C ++ 0x, os destruidores serão virtuais se o objeto tiver qualquer função virtual (ou seja, uma vtable).
Macke,

não se esqueça do NRVO e, claro, qualquer otimização é permitida, desde que não mude a saída do programa
jk.

26

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 Cobjeto, caso em que a classe Bnão tem negócios e não tem nenhuma pista sobre seus dados. Esse acesso só é concedido se xfor 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::stackexemplo.

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);
}


26

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".


1
Quando você diz que a resolução de sobrecarga é feita no resultado deles, você quer dizer que ele realmente converte para ambos os Functors e, em seguida, faz a resolução de sobrecarga? Tentei imprimir algo no operador Func1 * () e no operador Func2 * (), mas parece que ele escolheu o correto ao descobrir qual operador de conversão invocar.
navegador de

3
@navigator, sim, ele se converte conceitualmente em ambos e escolhe o melhor. Ele não precisa chamá-los de fato, porque sabe, pelo tipo de resultado, o que eles já produzirão. A chamada real é feita quando descobrir o que foi finalmente escolhido.
Johannes Schaub - litb

26

Recursos ocultos:

  1. Funções puras virtuais podem ter implementação. Exemplo comum, destruidor virtual puro.
  2. 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_exceptionem sua especificação de exceção, a exceção é convertida em std::bad_exceptione lançada automaticamente. Dessa forma, você saberá pelo menos que um bad_exceptionfoi lançado. Leia mais aqui .

  3. blocos de teste de função

  4. 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 .

  5. os padrões dos parâmetros da função podem ser alterados em tempo de execução. Leia mais aqui .

  6. A[i] funciona tão bem quanto i[A]

  7. 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 .

  8. 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)
    }

P Daddy: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Eu entendi a comutação, mas isso significa que [] não tem valor semântico próprio e é simplesmente equivalente a uma substituição de estilo macro onde "x [y]" é substituído por "(* ((x) + (y ))) ". Nem um pouco o que eu esperava. Eu me pergunto por que é definido dessa forma.
P papai

Compatibilidade com versões anteriores com C
jmucchiello

2
Com relação ao seu primeiro ponto: há um caso particular em que você precisa implementar uma função virtual pura: destruidores virtuais puros.
Frerich Raabe

24

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.


11
E na extremidade oposta você não pode usar o operador [] em um mapa const
David Rodríguez - dribeas

2
+1 para Nick, as pessoas podem enlouquecer se não souberem .find().
LiraNuna,

ou " const map::operator[]gera mensagens de erro"
apenas alguém

2
Não é um recurso da linguagem, é um recurso da biblioteca de modelos padrão. Também é bastante óbvio, já que operator [] retorna uma referência válida.
Ramon Zarazua B.

2
Tive que usar mapas em C # por enquanto, onde os mapas não se comportam dessa maneira, para perceber que isso é um recurso. Achei que estava mais irritado com ele do que com o uso, mas parece que estava errado. Estou perdendo isso em C #.
sbi de

20

Colocar funções ou variáveis ​​em um namespace sem nome torna obsoleto o uso de staticpara restringi-los ao escopo do arquivo.


"desaprova" é um termo forte ...
Potatoswatter

@Potato: Comentário antigo, eu sei, mas o padrão diz que o uso de estático no escopo do namespace está obsoleto, com preferência para namespaces não nomeados.
GManNickG

@GMan: sem problemas, não acho que as páginas de SO realmente "morram". Apenas para os dois lados da história, staticem escopo global não está obsoleto de forma alguma. (Para referência: C ++ 03 §D.2)
Potatoswatter

Ah, em uma leitura mais atenta, "Um nome declarado no namespace global tem escopo de namespace global (também chamado de escopo global)." Isso realmente significa isso?
Potatoswatter

@Potato: Sim. :) staticuse só deve ser usado em um tipo de classe ou função.
GManNickG

19

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


18

funções void podem retornar valores nulos

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 voidfunçõ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; !
};

17

Leia um arquivo em um vetor de strings:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Ou: vetor <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens de

5
você quer dizer vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- faltando parênteses após o segundo parâmetro
knittl

1
Este não é realmente um recurso oculto do C ++. Mais um recurso STL. STL! = Um idioma
Nick Bedford

14

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.


1
Veja aqui, onde recentemente sugeri para aritmética de n bits: stackoverflow.com/questions/8309538/…
ver

14

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 SomeTypeobjeto na pilha e o inicializam (com unos 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 SomeTypenomeado u.


há alguma diferença entre o primeiro e o segundo? embora, eu sei que ambos são inicializações.
Özgür

Comptrol: Acho que não. Ambos acabarão chamando o construtor de cópia, embora o primeiro PAREÇA como o operador de atribuição, ele é realmente o construtor de cópia.
abelenky

1
Se u for um tipo diferente de SomeType, o primeiro chamará o construtor de conversão primeiro e, em seguida, o construtor de cópia, enquanto o segundo chamará apenas o construtor de conversão.
Eclipse

3
A primeira é a chamada implícita do construtor, a segunda é a chamada explícita. Observe o código a seguir para ver a diferença: #include <iostream> class sss {public: explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (duplo) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // chama o construtor int sss xxx = 7; // chama o construtor duplo return 0; }
Kirill V. Lyadvinsky

True - a primeira linha não funcionará se o construtor for declarado explícito.
Eclipse

12

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;

2
FStruct s = {};é ainda mais curto.
Constantin,

No último exemplo, seria mais simples com: a (); b (); float c = 1.0f;
Zifre de

2
Esta sintaxe "float c = (a (), b (), 1.0f);" é útil para acentuar a operação de atribuição (atribuição de "c"). As operações de atribuição são importantes na programação porque têm menos probabilidade de se tornarem obsoletas na IMO. Não sei por que, pode ter algo a ver com programação funcional, onde o estado do programa é reatribuído a cada quadro. PS. E não, "int d = (11,22,1.0f)" será igual a "1". Testado há um minuto com VS2008.
AareP de

2
+1 Você não deveria estar ligando main ? Eu sugeriria global().main();e simplesmente esqueceria o singleton ( você pode apenas trabalhar com o temporário, que prolonga sua vida útil )
ver

1
Duvido que atribuir referências seja portátil. Eu amo a estrutura para dispensar declarações futuras.
Thomas Eding

12

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).


Pelo que vale a pena, Neil tem uma pergunta sobre isso: stackoverflow.com/questions/1212978/… , apenas para informações extras.
GManNickG

12

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.


1
Arrumado. Algum motivo específico que você incluiu struct Cem seu exemplo ...? Felicidades.
Tony Delroy
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.