Em C ++ 11, temos essa nova sintaxe para inicializar classes que nos dá um grande número de possibilidades de como inicializar variáveis.
{ // Example 1
int b(1);
int a{1};
int c = 1;
int d = {1};
}
{ // Example 2
std::complex<double> b(3,4);
std::complex<double> a{3,4};
std::complex<double> c = {3,4};
auto d = std::complex<double>(3,4);
auto e = std::complex<double>{3,4};
}
{ // Example 3
std::string a(3,'x');
std::string b{3,'x'}; // oops
}
{ // Example 4
std::function<int(int,int)> a(std::plus<int>());
std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
std::unique_ptr<int> a(new int(5));
std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
std::locale::global(std::locale("")); // copied from 22.4.8.3
std::locale::global(std::locale{""});
}
{ // Example 7
std::default_random_engine a {}; // Stroustrup's FAQ
std::default_random_engine b;
}
{ // Example 8
duration<long> a = 5; // Stroustrup's FAQ too
duration<long> b(5);
duration<long> c {5};
}
Para cada variável que declaro, tenho que pensar qual sintaxe de inicialização devo usar e isso diminui minha velocidade de codificação. Tenho certeza de que não foi essa a intenção de introduzir as chaves.
Quando se trata de código de modelo, alterar a sintaxe pode levar a diferentes significados, portanto, seguir o caminho certo é essencial.
Eu me pergunto se existe uma diretriz universal sobre a sintaxe que devemos escolher.
c++
c++11
initializer-list
Helami
fonte
fonte
Respostas:
Acho que o seguinte pode ser uma boa diretriz:
Se o valor (único) com o qual você está inicializando pretende ser o valor exato do objeto, use a
=
inicialização de copy ( ) (porque então, em caso de erro, você nunca invocará acidentalmente um construtor explícito, que geralmente interpreta o valor fornecido diferente). Em locais onde a inicialização da cópia não está disponível, veja se a inicialização da chave tem a semântica correta e, em caso afirmativo, use-a; caso contrário, use a inicialização entre parênteses (se isso também não estiver disponível, você está sem sorte de qualquer maneira).Se os valores com os quais você está inicializando forem uma lista de valores a serem armazenados no objeto (como os elementos de um vetor / matriz ou parte real / imaginária de um número complexo), use a inicialização de chaves, se disponível.
Se os valores com os quais você está inicializando não são valores a serem armazenados, mas descrevem o valor / estado pretendido do objeto, use parênteses. Os exemplos são o argumento de tamanho de a
vector
ou o argumento do nome de arquivo de umfstream
.fonte
T {}
ou motivos sintáticos, como a análise mais incômoda ), mas em geral acho que esse é um bom conselho. Observe que esta é minha opinião subjetiva, portanto, deve-se dar uma olhada nas outras respostas também.type var{};
faz.Tenho certeza de que nunca haverá uma diretriz universal. Minha abordagem é usar sempre chaves, lembrando que
Portanto, as chaves redondas e as chaves não são intercambiáveis. Mas saber onde eles diferem me permite usar a inicialização de colchetes curvos sobre colchetes na maioria dos casos (alguns dos casos em que não consigo são erros de compilador).
fonte
int i = 0;
, não acho que alguém usariaint i{0}
lá, e pode ser confuso (também,0
é tipo ifint
, então não haveria estreitamento ). Para todo o resto, seguiria o conselho de Juancho: prefira {}, cuidado com os poucos casos em que não o deve fazer. Observe que não há muitos tipos que aceitarão listas de inicializadores como argumentos de construtor, você pode esperar que contêineres e tipos semelhantes a contêiner (tupla ...) os tenham, mas a maioria do código chamará o construtor apropriado.int i{some floating point}
é um erro, em vez de truncar silenciosamente.{}
para significar "inicializar", a menos que seja absolutamente impossível .Fora do código genérico (ou seja, modelos), você pode (e eu faço) usar colchetes em qualquer lugar . Uma vantagem é que funciona em qualquer lugar, por exemplo, até mesmo para inicialização em classe:
ou para argumentos de função:
Para variáveis que não presto muita atenção entre os estilos
T t = { init };
ouT t { init };
, acho que a diferença é pequena e, na pior das hipóteses, só resultará em uma mensagem útil do compilador sobre o uso incorreto de umexplicit
construtor.Para tipos que aceitam,
std::initializer_list
embora às vezes, obviamente, os não-std::initializer_list
construtores são necessários (sendo o exemplo clássicostd::vector<int> twenty_answers(20, 42);
). Não há problema em não usar aparelho ortodôntico.Quando se trata de código genérico (ou seja, em modelos), o último parágrafo deve ter gerado alguns avisos. Considere o seguinte:
Em seguida,
auto p = make_unique<std::vector<T>>(20, T {});
cria um vetor de tamanho 2 seT
for egint
, ou um vetor de tamanho 20 seT
forstd::string
. Um sinal muito revelador de que há algo muito errado acontecendo aqui é que não há nenhum traço que pode salvá-lo aqui (por exemplo, com SFINAE):std::is_constructible
é em termos de inicialização direta, enquanto estamos usando a inicialização de chave que adia para dirigir inicialização se e somente se não houver nenhum construtorstd::initializer_list
interferindo. Da mesma formastd::is_convertible
não ajuda em nada.Eu investiguei se é de fato possível rolar à mão uma característica que pode consertar isso, mas não estou muito otimista sobre isso. Em todo caso, não acho que estaríamos perdendo muito, acho que o fato de
make_unique<T>(foo, bar)
resultar em uma construção equivalente aT(foo, bar)
é muito intuitivo; especialmente porquemake_unique<T>({ foo, bar })
é bastante diferente e só faz sentido sefoo
ebar
tiver o mesmo tipo.Portanto, para código genérico, eu só uso chaves para inicialização de valor (por exemplo,
T t {};
ouT t = {};
), o que é muito conveniente e acho superior ao método C ++ 03T t = T();
. Caso contrário, é a sintaxe de inicialização direta (ou sejaT t(a0, a1, a2);
), ou às vezes a construção padrão (T t; stream >> t;
sendo o único caso em que eu uso isso, eu acho).Isso não significa que todas as chaves sejam ruins, considere o exemplo anterior com correções:
Isso ainda usa colchetes para construir o
std::unique_ptr<T>
, embora o tipo real dependa do parâmetro do modeloT
.fonte
make_unique<T>(20u, T {})
paraT
serunsigned
oustd::string
. Não tenho muita certeza dos detalhes. (Observe que eu também comentei sobre as expectativas em relação à inicialização direta vs inicialização de chaves, por exemplo, funções de encaminhamento perfeito.)std::string c("qux");
Não foi especificado para funcionar como uma inicialização em classe para evitar ambigüidades com declarações de função de membro na gramática.