it-swarm-pt.tech

Recursos ocultos do C ++?

Não gosta de C++ quando se trata dos "recursos ocultos" da linha de perguntas? Achei que eu jogaria lá fora. Quais são alguns dos recursos ocultos do C++?

114
Craig H

A maioria dos programadores de C++ está familiarizada com o operador ternário:

x = (y < 0) ? 10 : 20;

No entanto, eles não percebem que ele pode ser usado como um lvalue:

(a == 0 ? a : b) = 1;

que é uma abreviação de

if (a == 0)
    a = 1;
else
    b = 1;

Use com cuidado :-)

308
Ferruccio

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

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

    ...
}
238
Ben

Aritmética de ponteiro.

Os programadores de C++ preferem evitar ponteiros devido aos erros que podem ser introduzidos.

O C++ mais legal que eu já vi? Literais analógicos.

140
Anonymouse

Eu concordo com a maioria das postagens: o C++ é uma linguagem com vários paradigmas; portanto, os recursos "ocultos" que você encontrará (além de "comportamentos indefinidos" que você deve evitar a todo custo) são usos inteligentes das instalações.

A maioria dessas instalações não são recursos integrados do idioma, mas baseados em bibliotecas.

O mais importante é o RAII, frequentemente ignorado por anos por desenvolvedores de C++ vindos do mundo C. Sobrecarga do operador geralmente é um recurso incompreendido que permite o comportamento do tipo matriz (operador subscrito), operações tipo ponteiro (ponteiros inteligentes) e operações integradas (matrizes multiplicadoras).

O uso de exceção geralmente é difícil, mas com algum trabalho, pode produzir código realmente robusto através de segurança de exceção especificações (incluindo código que não falhará ou que terá um recursos do tipo commit que serão bem-sucedidos ou retornarão ao seu estado original).

O recurso mais famoso do "oculto" do C++ é metaprogramming, pois permite que seu programa seja parcialmente (ou totalmente) executado em tempo de compilação, em vez de em tempo de execução. Porém, isso é difícil e você deve ter uma sólida compreensão dos modelos antes de tentar.

Outros fazem uso do paradigma múltiplo para produzir "formas de programação" fora do ancestral do C++, ou seja, C.

Usando functors, você pode simular funções, com a segurança de tipo adicional e com estado. Usando o padrão command, você pode atrasar a execução do código. A maioria dos outros padrões de design pode ser fácil e eficientemente implementada em C++ para produzir estilos de codificação alternativos que não deveriam estar na lista de "paradigmas oficiais de C++".

Usando templates, você pode produzir código que funcionará na maioria dos tipos, incluindo o que você pensou inicialmente. Você também pode aumentar a segurança do tipo (como um typesafe automatizado malloc/realloc/free). Os recursos do objeto C++ são realmente poderosos (e, portanto, perigosos se usados ​​de maneira descuidada), mas mesmo o polimorfismo dinâmico tem sua versão estática no C++: o CRTP.

Eu descobri que a maioria dos livros do tipo " C++ " eficazes de Scott Meyers ou " livros do tipo C++ " excepcionais do Herb Sutter ser de fácil leitura e tesouros de informações sobre os recursos conhecidos e menos conhecidos do C++.

Entre os meus preferidos, está um que deve fazer com que os cabelos de qualquer programador Java surjam de horror: em C++, a maneira mais orientada a objetos de adicionar um recurso a um objeto é por meio de um membro não membro. função de amigo, em vez de uma função de membro (ou seja, método de classe), porque:

  • No C++, a interface de uma classe é tanto sua função membro quanto a função não membro no mesmo espaço para nome

  • funções de não-membro não-amigo não têm acesso privilegiado à classe interna. Dessa forma, o uso de uma função membro sobre uma não-membro não amiga enfraquecerá o encapsulamento da classe.

Isso nunca deixa de surpreender até os desenvolvedores experientes.

(Fonte: Entre outros, o Guru da Semana # 84 de Herb Sutter: http://www.gotw.ca/gotw/084.htm )

119
paercebal

Um recurso de idioma que considero um pouco oculto, porque nunca ouvi falar dele durante todo o meu tempo na escola, é o alias do namespace. Não foi trazido à minha atenção até encontrar exemplos disso na documentação do impulso. Obviamente, agora que eu sei disso, você pode encontrá-lo em qualquer referência C++ padrão.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );
118
Jason Mock

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

102
Johannes Schaub - litb

O operador de matriz é associativo.

A [8] é sinônimo de * (A + 8). Como adição é associativa, pode ser reescrita como * (8 + A), que é sinônimo de ..... 8 [A]

Você não disse útil ... :-)

77
Colin Jensen

Uma coisa que é pouco conhecida é 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. Apenas nada que tenha a ver com herança (incluindo funções virtuais).

73
Johannes Schaub - litb

C++ é um padrão, não deve haver nenhum recurso oculto ...

C++ é uma linguagem de paradigmas múltiplos, você pode apostar seu último dinheiro em recursos ocultos. Um exemplo dentre muitos: metaprogramação de modelo . Ninguém no comitê de padrões pretendia que houvesse uma sub-linguagem completa de Turing executada em tempo de compilação.

72
Konrad Rudolph

Outro recurso oculto que não funciona em C é a funcionalidade do operador + unário. Você pode usá-lo para promover e deteriorar todo tipo de coisas

Convertendo uma enumeração em um número inteiro

+AnEnumeratorValue

E o valor do seu enumerador que anteriormente tinha seu tipo de enumeração agora tem o tipo inteiro perfeito que pode se ajustar ao seu valor. Manualmente, você dificilmente conheceria esse tipo! Isso é necessário, por exemplo, quando você deseja implementar um operador sobrecarregado para sua enumeração.

Obter o valor de uma variável

Você precisa usar uma classe que usa um inicializador estático dentro 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 em 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);
}

Deteriorar uma matriz para um ponteiro

Deseja passar dois ponteiros para uma função, mas simplesmente não funciona? O operador 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
}
66
Johannes Schaub - litb

A vida de temporários vinculados a referências const é uma que poucas pessoas conhecem. Ou pelo menos é o meu conhecimento favorito em C++ que a maioria das pessoas não conhece.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope
61
MSN

Um recurso interessante 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 converter exceção para outra classe de exceção e relançar, ou converter entre exceções e manipulação de código de erro baseado em retorno.

52
vividos

Muitos conhecem a metafunção identity/id, mas existe uma boa maneira de usá-la para casos que não são modelos: Facilidade de escrever 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;

Ajuda muito a descriptografar declarações C++!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };
44
Johannes Schaub - litb

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 um 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) { /* ... */ }
}

O BOOST_FOREACH também o usa sob o capô. Para concluir 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). Mas não tenho muita certeza se isso é útil :)

43
Johannes Schaub - litb

Impedindo que o operador de vírgula chame sobrecargas de operador

Às vezes, você faz uso válido do operador de vírgula, mas deseja garantir que nenhum operador de vírgula definido pelo usuário atrapalhe, porque, por exemplo, você depende de pontos de sequência entre o lado esquerdo e o direito ou deseja garantir que nada interfira no desejado açao. É aqui que void() entra em jogo:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Ignore os espaços reservados que eu coloquei para a condição e o código. O importante é a void(), que faz o compilador forçar o uso do operador de vírgula embutido. Isso também pode ser útil na implementação de classes de características.

29
Johannes Schaub - litb

Inicialização de matriz 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 o padrão (aqui todos os elementos da matriz são zero) no construtor como:

clName::clName() : a()
{
}
28
Poorna

Oooh, posso criar uma lista de ódios para 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 eles não são
  • Clases locais não podem ser usados ​​como parâmetros de modelo (os tornam menos úteis)
  • especificadores de exceção: parecem úteis, mas não são
  • sobrecargas de função ocultam funções da classe base com diferentes assinaturas.
  • nenhuma padronização útil na internacionalização (conjunto de caracteres padrão portátil amplo, alguém? Vamos ter que esperar até C++ 0x)

Do lado positivo

  • recurso oculto: função tente blocos. Infelizmente eu não encontrei um uso para isso. Sim, eu sei por que eles o adicionaram, mas você deve refazer o processo em um construtor, o que o torna inútil.
  • Vale a pena examinar atentamente as garantias do STL sobre a validade do iterador após a modificação do contêiner, o que pode permitir que você faça alguns loops um pouco mais agradáveis.
  • Impulso - dificilmente é um segredo, mas vale a pena usá-lo.
  • Otimização do valor de retorno (não óbvio, mas é especificamente permitido pelo padrão)
  • Functors aka objetos de função aka operator (). Isso é usado extensivamente pelo STL. não é realmente um segredo, mas é um efeito colateral bacana da sobrecarga e modelos do operador.
27
Robert

Você pode acessar dados protegidos e membros da função de qualquer classe, sem comportamento indefinido e com semântica esperada. Leia para ver como. Leia também o relatório de defeitos sobre isso.

Normalmente, o C++ proíbe o acesso a membros protegidos não estáticos do objeto de uma classe, mesmo que essa classe seja 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 o que a referência realmente aponta. Poderia ser um objeto C, nesse caso a classe B não tem nenhum negócio e pista sobre seus dados. Esse acesso é concedido apenas se x for uma referência a uma classe derivada ou a uma derivada dela. E pode permitir que um código arbitrário leia qualquer membro protegido, apenas criando uma classe "throw-away" que lê membros, por exemplo de 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 indicadores de membros permitem contornar essa proteção! O ponto principal é que o tipo de um ponteiro de membro está vinculado à classe que realmente contém esse membro - não à classe que você especificou ao fazer 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 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) {
            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);
}
27
Johannes Schaub - litb

Recursos ocultos:

  1. Funções virtuais puras podem ter implementação. Exemplo comum, destruidor virtual puro.
  2. Se uma função lança uma exceção não listada em suas especificações de exceção, mas a função possui 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ê pelo menos saberá que um bad_exception foi lançado. Leia mais aqui .

  3. blocos de tentativa de função

  4. A palavra-chave template em typedefs desambiguantes em um template de classe. Se o nome de uma especialização de modelo de membro aparecer após um operador ., -> ou :: e esse nome tiver parâmetros de modelo explicitamente qualificados, prefixe o nome do modelo de membro com o modelo de palavra-chave. Leia mais aqui .

  5. os padrões dos parâmetros de 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 do : 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)
    }
    
26
Sumant

Outro recurso oculto é que você pode chamar objetos de classe que podem ser convertidos em indicadores ou referências de função. A resolução de 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
}

Eles são chamados de "funções de chamada substitutas".

26
Johannes Schaub - litb

map::operator[] cria entrada se a chave estiver ausente 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 impressionado com quantos programadores de C++ não sabem disso.

24
Constantin

A colocação de funções ou variáveis ​​em um espaço de nome sem nome substitui o uso de static para restringi-los ao escopo do arquivo.

20
Jim Hunziker

A definição de funções comuns de amigo nos modelos de classe precisa de 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

Portanto, devemos garantir que os parâmetros do modelo de classe apareçam no tipo de qualquer função de amigo definida nesse modelo (a menos que desejemos impedir mais de uma instanciação de um modelo de classe em um arquivo específico, mas isso é pouco prová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 em C++ Templates: The Complete Guide /Seção 8.4

19
Özgür

funções void podem retornar valores void

Pouco conhecido, mas o código a seguir está correto

void f() { }
void g() { return f(); }

Além da seguinte aparência estranha

void f() { return (void)"i'm discarded"; }

Sabendo disso, você pode tirar proveito em algumas áreas. Um exemplo: as funções void não podem retornar um valor, mas você também não pode apenas 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; !
};
18
Johannes Schaub - litb

Leia um arquivo em um vetor de strings:

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

istream_iterator

17
Jason Baker

Uma das gramáticas mais interessantes de todas as linguagens de programação.

Três dessas coisas estão juntas, e duas são algo completamente diferente ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Todos, exceto o terceiro e o quinto, definem um objeto SomeType 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 aceita parâmetros e retorna um SomeType. O quinto está declarando da mesma forma uma função que aceita um parâmetro pelo valor do tipo SomeType chamado u.

14
Eclipse

Você pode modelar campos de bits.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Ainda estou com algum objetivo para isso, mas com certeza me surpreendeu.

14
Kaz Dragon

A regra de dominância é útil, mas pouco conhecida. Ele diz que, mesmo que em um caminho não exclusivo através de uma rede de classe base, a pesquisa de nome para um membro parcialmente oculto será exclusiva 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(); } };

Eu usei isso para implementar o alinhamento-suporte 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. Eu já vi isso implementar características substituíveis em metaprogramas.

12
Johannes Schaub - litb

O operador condicional ternário ?: exige que seu segundo e terceiro operando tenham tipos "agradáveis" (falando informalmente). Mas esse requisito possui uma exceção (trocadilho intencional): o segundo ou o terceiro operando pode ser uma expressão throw (que possui o tipo void), independentemente do tipo do outro operando.

Em outras palavras, é possível escrever as seguintes expressões C++ válidas de forma anterior usando o operador ?:

i = a > b ? a : throw something();

BTW, o fato de que a expressão throw é realmente ma expressão (do tipo void) e não uma declaraçã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 faça muito sentido fazê-lo dessa maneira (talvez em algum código de modelo genérico isso possa ser útil).

12
AnT

Livrar-se de declarações avançadas:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

Escrevendo declarações de chave com?: Operadores:

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

Fazendo tudo em uma única linha:

void a();
int b();
float c = (a(),b(),1.0f);

Zerando estruturas sem memset:

FStruct s = {0};

Normalizando/agrupando valores de ângulo e tempo:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

Atribuindo referências:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
12
AareP

Eu achei este blog um recurso incrível sobre as arcadas de C++: C++ Truths .

9
Drealmer

Um segredo perigoso é

Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();

Meu segredo favorito que raramente vejo usado:

class A
{
};

struct B
{
  A a;
  operator A&() { return a; }
};

void func(A a) { }

int main()
{
  A a, c;
  B b;
  a=c;
  func(b); //yeah baby
  a=b; //gotta love this
}
8
user34537

As aulas locais são impressionantes:

struct MyAwesomeAbstractClass
{ ... };


template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
    struct ans : MyAwesomeAbstractClass
    {
        // Make the implementation depend on T
    };

    return new ans(...);
}

bastante elegante, já que não polui o espaço para nome com definições de classe inúteis ...

8
Alexandre C.

Um recurso oculto, mesmo oculto para desenvolvedores do GCC , é inicializar um membro da matriz usando uma string literal. Suponha que você tenha uma estrutura que precise trabalhar com uma matriz C e deseje inicializar o membro da matriz com um conteúdo padrão

struct Person {
  char name[255];
  Person():name("???") { }
};

Isso funciona e funciona apenas com matrizes de caracteres e inicializadores literais de cadeias. Não é necessário strcpy!

7
Johannes Schaub - litb

Um exemplo dentre muitos: metaprogramação de modelos. Ninguém no comitê de padrões pretendia que houvesse uma sub-linguagem completa de Turing executada em tempo de compilação.

A metaprogramação de modelos dificilmente é um recurso oculto. É mesmo na biblioteca de impulso. Veja MPL . Mas se "quase oculto" for bom o suficiente, dê uma olhada no boost libraries . Ele contém muitos itens que não são fáceis de acessar sem o apoio de uma biblioteca forte.

Um exemplo é a biblioteca boost.lambda , o que é interessante, pois o C++ não possui funções lambda no padrão atual.

Outro exemplo é Loki , que "faz uso extensivo da metaprogramação de modelos C++ e implementa várias ferramentas comumente usadas: lista de tipos, functor, singleton, ponteiro inteligente, fábrica de objetos, visitante e métodos múltiplos." [ Wikipedia ]

6
Markowitch

Não há recursos ocultos, mas a linguagem C++ é muito poderosa e, freqüentemente, nem os desenvolvedores de padrões conseguem imaginar para que C++ pode ser usado.

Na verdade, a partir de uma construção de linguagem bastante simples, você pode escrever algo muito poderoso. Muitas dessas coisas estão disponíveis em www.boost.org como exemplos (e http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html entre elas ).

Para entender como a construção simples de linguagem pode ser combinada com algo poderoso, é bom ler "C++ Templates: The Complete Guide", de David Vandevoorde, Nicolai M. Josuttis e realmente livro mágico "Modern C++ Design ..." de Andrei Alexandresc .

E, finalmente, é difícil aprender C++, você deve tentar preenchê-lo;)

5
sergtk

Parece-me que poucas pessoas sabem sobre namespaces não nomeados:

namespace {
  // Classes, functions, and objects here.
}

Os espaços para nome sem nome se comportam como se tivessem sido substituídos por:

namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
  // original namespace body
}

".. onde todas as ocorrências deste [nome exclusivo] em uma unidade de tradução são substituídas pelo mesmo identificador e esse identificador difere de todos os outros identificadores em todo o programa." [C++ 03, 7.3.1.1/1]

4
vobject
4
Özgür

Não tenho certeza sobre o oculto, mas existem alguns interessantes'truques' que provavelmente não são óbvios apenas lendo as especificações.

3
dbrien

Há muitos "comportamentos indefinidos". Você pode aprender como evitá-los lendo bons livros e lendo os padrões.

3
ugasoft

A maioria dos desenvolvedores de C++ ignora o poder da metaprogramação de modelos. Confira Loki Libary . Ele implementa várias ferramentas avançadas, como lista de tipos, functor, singleton, ponteiro inteligente, fábrica de objetos, visitante e métodos múltiplos, usando extensamente a metaprogramação de modelos (de wikipedia ). Para a maior parte, você pode considerá-los como um recurso c ++ "oculto".

3
Sridhar Iyer

De Verdades em C++ .

Definir funções com assinaturas idênticas no mesmo escopo, portanto, isso é legal:

template<class T> // (a) a base template
void f(T) {
  std::cout << "f(T)\n";
}

template<>
void f<>(int*) { // (b) an explicit specialization
  std::cout << "f(int *) specilization\n";
}

template<class T> // (c) another, overloads (a)
void f(T*) {
  std::cout << "f(T *)\n";
}

template<>
void f<>(int*) { // (d) another identical explicit specialization
  std::cout << "f(int *) another specilization\n";
}
3
Özgür
  • ponteiros para métodos de classe
  • A palavra-chave "typename"
3
shoosh
3
sdcvvc

main () não precisa de um valor de retorno:

int main(){}

é o programa C++ válido mais curto.

2
Jeffrey Faust

Preste atenção à diferença entre o ponteiro de função livre e as inicializações do ponteiro de função de membro:

função membro:

struct S
{
 void func(){};
};
int main(){
void (S::*pmf)()=&S::func;//  & is mandatory
}

e função livre:

void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well; 
}

Graças a este & redundante, você pode adicionar manipuladores de fluxo - que são funções livres - em cadeia sem ele:

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
2
Özgür
  1. map::insert(std::pair(key, value)); não substitui se o valor da chave já existe.

  2. Você pode instanciar uma classe logo após sua definição: (devo acrescentar que esse recurso me deu centenas de erros de compilação por causa do ponto-e-vírgula ausente e nunca vi alguém usá-lo nas classes)

    class MyClass {public: /* code */} myClass;
    
2
Viktor Sehr

Existem toneladas de construções "complicadas" em C++. Eles vão de implementações "simples" de classes seladas/finais usando herança virtual. E chegue a construções de meta-programação bastante "complexas", como as de MPL ( tutorial ) de Boost. As possibilidades de se dar um tiro no pé são infinitas, mas, se mantidas sob controle (ou seja, programadores experientes), oferecem a melhor flexibilidade em termos de manutenção e desempenho.

1
Amir

As chaves de classe class e struct são quase idênticas. A principal diferença é que as classes assumem como padrão o acesso privado para membros e bases, enquanto as estruturas assumem como padrão o público:

// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };

// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };

// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B

Os sindicatos também podem ter membros e métodos e usar como padrão o acesso público de maneira semelhante às estruturas.

1
a_m0d

Idioma de conversão indireta :

Suponha que você esteja projetando uma classe de ponteiro inteligente. Além de sobrecarregar os operadores * e ->, uma classe de ponteiro inteligente geralmente define um operador de conversão para bool:

template <class T>
class Ptr
{
public:
 operator bool() const
 {
  return (rawptr ? true: false);
 }
//..more stuff
private:
 T * rawptr;
};

A conversão para bool permite que os clientes usem ponteiros inteligentes em expressões que exigem operandos bool:

Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
 cout<<"int value is: "<<*ptr <<endl;
else
 cout<<"empty"<<endl;

Além disso, a conversão implícita em bool é necessária em declarações condicionais, como:

if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
 //we get here only of px isn't empty
} 

Infelizmente, essa conversão automática abre o portão para surpresas indesejadas:

Ptr <int> p1;
Ptr <double> p2;

//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl; 
//prints 0, 1, or 2, although there isn't an overloaded operator+()

Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated 

//surprise #2
if(pf==pq) //compares bool values, not pointers! 

Solução: use o idioma "conversão indireta", convertendo de ponteiro para membro de dados [pMember] em bool, para que haja apenas 1 conversão implícita, o que impedirá o comportamento inesperado mencionado: pMember-> bool em vez de bool-> algo outro.

1
Özgür

Se o operador delete () usa o argumento size além de * void, isso significa que ele será altamente uma classe base. Esse argumento de tamanho torna possível verificar o tamanho dos tipos para destruir o correto. Aqui o que Stephen Dewhurst diz sobre isso:

Observe também que empregamos uma versão de dois argumentos da exclusão do operador, em vez da versão usual de um argumento. Essa versão de dois argumentos é outra versão "usual" da exclusão do operador membro frequentemente empregada pelas classes base que esperam que as classes derivadas herdem sua implementação de exclusão do operador. O segundo argumento conterá o tamanho do objeto que está sendo excluído - informações que geralmente são úteis na implementação do gerenciamento de memória personalizado.

1
Özgür

Eu acho muito instável as instâncias recursivas de modelos:

template<class int>
class foo;

template
class foo<0> {
    int* get<0>() { return array; }
    int* array;  
};

template<class int>
class foo<i> : public foo<i-1> {
    int* get<i>() { return array + 1; }  
};

Eu usei isso para gerar uma classe com 10 a 15 funções que retornam ponteiros para várias partes de uma matriz, pois uma API que eu usei exigia um ponteiro de função para cada valor.

I.e. programar o compilador para gerar várias funções, via recursão. Fácil como torta. :)

1
Macke

Meu favorito (por enquanto) é a falta de semântica em uma declaração como A = B = C. Qual o valor de A é basicamente indeterminado.

Pense nisso:

class clC
{
public:
   clC& operator=(const clC& other)
   {
      //do some assignment stuff
      return copy(other);
   }
   virtual clC& copy(const clC& other);
}

class clB : public clC
{
public:
  clB() : m_copy()
  {
  }

  clC& copy(const clC& other)
  {
    return m_copy;
  }

private:
  class clInnerB : public clC
  {
  }
  clInnerB m_copy;
}

agora A pode ser de um tipo inacessível a outros objetos que não sejam do tipo clB e ter um valor não relacionado a C.

0
Rune FS

Adicionando restrições aos modelos.

0
Özgür

Ponteiros de membro e operador de ponteiro de membro -> *

#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
    A* a = new A();
    a->d = 8;
    printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
    return 0;
}

Para métodos (a -> * & A :: e) () é um pouco como Function.call () de javascript

var f = A.e
f.call(a) 

Para os membros, é um pouco como acessar com o operador []

a['d']
0
Kamil Szot

Você pode exibir todas as macros predefinidas por meio de opções de linha de comando com alguns compiladores. Isso funciona com gcc e icc (compilador C++ da Intel):

$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt

Para o MSVC, eles estão listados em local único . Eles também podem ser documentados em um único local para os outros, mas com os comandos acima, você pode claramente ver o que é e o que não é definido e exatamente o que são usados ​​valores após a aplicação de todas as outras opções da linha de comandos.

Comparar (após a classificação):

 $ diff gxx-macros.txt icx-macros.txt
 $ diff gxx-macros.txt gcc-macros.txt
 $ diff icx-macros.txt icc-macros.txt
0
Roger Pate
class Empty {};

namespace std {
  // #1 specializing from std namespace is okay under certain circumstances
  template<>
  void swap<Empty>(Empty&, Empty&) {} 
}

/* #2 The following function has no arguments. 
   There is no 'unknown argument list' as we do
   in C.
*/
void my_function() { 
  cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
  // and this doesn't affect things outside of that scope
}

int main() {
  using namespace std; /* #4 you can use using in function scopes */
  cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
  /* #6 falling off of main without an explicit return means "return 0;" */
}
0
dirkgently