Eu não entendo por que eu faria isso:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
Por que não dizer:
S() {} // instead of S() = default;
por que trazer uma nova sintaxe para isso?
Eu não entendo por que eu faria isso:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
Por que não dizer:
S() {} // instead of S() = default;
por que trazer uma nova sintaxe para isso?
Respostas:
Um construtor padrão padrão é definido especificamente como sendo o mesmo que um construtor padrão definido pelo usuário sem lista de inicialização e uma instrução composta vazia.
§12.1 / 6 [class.ctor] Um construtor padrão que é padronizado e não definido como excluído é implicitamente definido quando é usado por odr para criar um objeto de seu tipo de classe ou quando é explicitamente padronizado após sua primeira declaração. O construtor padrão definido implicitamente executa o conjunto de inicializações da classe que seria executado por um construtor padrão escrito pelo usuário para essa classe sem o inicializador de ctor (12.6.2) e uma instrução composta vazia. [...]
No entanto, enquanto os dois construtores se comportam da mesma forma, fornecer uma implementação vazia afeta algumas propriedades da classe. Fornecer um construtor definido pelo usuário, mesmo que não faça nada, torna o tipo não agregado e também trivial . Se você deseja que sua classe seja um tipo agregado ou trivial (ou por transitividade, um tipo de POD), será necessário usá-lo = default
.
§8.5.1 / 1 [dcl.init.aggr] Um agregado é uma matriz ou uma classe sem construtores fornecidos pelo usuário, [e ...]
§12.1 / 5 [class.ctor] Um construtor padrão é trivial se não for fornecido pelo usuário e [...]
§9 / 6 [classe] Uma classe trivial é uma classe que possui um construtor padrão trivial e [...]
Para demonstrar:
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() { };
};
int main() {
static_assert(std::is_trivial<X>::value, "X should be trivial");
static_assert(std::is_pod<X>::value, "X should be POD");
static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}
Além disso, a inadimplência explícita de um construtor o fará constexpr
se o construtor implícito tivesse sido e também fornecerá a mesma especificação de exceção que o construtor implícito teria. No caso que você forneceu, o construtor implícito não seria constexpr
(porque deixaria um membro de dados não inicializado) e também teria uma especificação de exceção vazia, portanto, não há diferença. Mas sim, no caso geral, você pode especificar manualmente constexpr
e a especificação de exceção para corresponder ao construtor implícito.
O uso = default
traz alguma uniformidade, pois também pode ser usado com construtores e destruidores de copiar / mover. Um construtor de cópia vazio, por exemplo, não fará o mesmo que um construtor de cópia padrão (que executará uma cópia de membro de seus membros). O uso uniforme da sintaxe = default
(ou = delete
) para cada uma dessas funções-membro especiais facilita a leitura do código, declarando explicitamente sua intenção.
constexpr
construtor (7.1.5), o construtor padrão definido implicitamente é constexpr
".
constexpr
se a declaração implícita fosse, (b) é implicitamente considerada como tendo a mesma declaração. especificação de exceção como se tivesse sido implicitamente declarada (15.4), ... "Não faz diferença neste caso específico, mas em geral foo() = default;
tem uma pequena vantagem sobre foo() {}
.
constexpr
(uma vez que um membro de dados é deixado não inicializado) e sua especificação de exceção permite todas as exceções. Eu vou deixar isso mais claro.
constexpr
(o que você mencionou não deve fazer diferença aqui): struct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};
Somente s1
dá um erro, não s2
. Tanto no clang como no g ++.
Eu tenho um exemplo que mostrará a diferença:
#include <iostream>
using namespace std;
class A
{
public:
int x;
A(){}
};
class B
{
public:
int x;
B()=default;
};
int main()
{
int x = 5;
new(&x)A(); // Call for empty constructor, which does nothing
cout << x << endl;
new(&x)B; // Call for default constructor
cout << x << endl;
new(&x)B(); // Call for default constructor + Value initialization
cout << x << endl;
return 0;
}
Resultado:
5
5
0
Como podemos ver, a chamada para o construtor A () vazio não inicializa os membros, enquanto B () o faz.
O n2210 fornece alguns motivos:
O gerenciamento de padrões tem vários problemas:
- As definições do construtor são acopladas; declarar qualquer construtor suprime o construtor padrão.
- O padrão do destruidor é inadequado para as classes polimórficas, exigindo uma definição explícita.
- Depois que um padrão é suprimido, não há como ressuscitá-lo.
- As implementações padrão geralmente são mais eficientes do que as implementações especificadas manualmente.
- Implementações não padrão não são triviais, o que afeta a semântica de tipos, por exemplo, torna um tipo não POD.
- Não há como proibir uma função de membro especial ou operador global sem declarar um substituto (não trivial).
type::type() = default; type::type() { x = 3; }
Em alguns casos, o corpo da classe pode mudar sem exigir uma alteração na definição da função de membro porque o padrão muda com a declaração de membros adicionais.
Veja Regra de três torna-se regra de cinco com C ++ 11? :
Observe que o construtor move e o operador de atribuição de movimentação não serão gerados para uma classe que declare explicitamente nenhuma das outras funções-membro especiais, que o construtor de cópia e o operador de atribuição de cópia não serão gerados para uma classe que declare explicitamente um construtor ou movimentação de movimento operador de atribuição e que uma classe com um destruidor declarado explicitamente e um construtor de cópias definido implicitamente ou um operador de atribuição de cópia definido implicitamente seja considerada obsoleta
= default
em geral, e não razões para fazer = default
em um construtor versus fazer { }
.
{}
já era uma característica da linguagem antes da introdução de =default
, estas razões não confiar implicitamente na distinção (por exemplo, "não há meios para ressuscitar [a padrão suprimido]" implica que {}
é não equivalente ao padrão )
É uma questão de semântica em alguns casos. Não é muito óbvio com os construtores padrão, mas se torna óbvio com outras funções-membro geradas pelo compilador.
Para o construtor padrão, seria possível fazer com que qualquer construtor padrão com um corpo vazio fosse considerado candidato a um construtor trivial, assim como o uso =default
. Afinal, os antigos construtores padrão vazios eram legais em C ++ .
struct S {
int a;
S() {} // legal C++
};
Se o compilador entende ou não esse construtor como trivial é irrelevante na maioria dos casos, fora das otimizações (manuais ou do compilador).
No entanto, essa tentativa de tratar os corpos de funções vazios como "padrão" é totalmente quebrada para outros tipos de funções de membro. Considere o construtor de cópia:
struct S {
int a;
S() {}
S(const S&) {} // legal, but semantically wrong
};
No caso acima, o construtor de cópias gravado com um corpo vazio agora está errado . Na verdade, não está mais copiando nada. Esse é um conjunto de semântica muito diferente da semântica padrão do construtor de cópias. O comportamento desejado requer que você escreva algum código:
struct S {
int a;
S() {}
S(const S& src) : a(src.a) {} // fixed
};
Mesmo com esse caso simples, no entanto, está se tornando muito mais difícil para o compilador verificar se o construtor de cópias é idêntico ao que ele geraria ou para ver que o construtor de cópias é trivial (equivalente a ummemcpy
, basicamente ) O compilador precisaria verificar a expressão de cada membro inicializador e garantir que seja idêntico à expressão para acessar o membro correspondente da fonte e nada mais, garantir que nenhum membro seja deixado com uma construção padrão não trivial etc. É um processo inverso o compilador usaria para verificar se suas próprias versões geradas dessa função são triviais.
Considere então o operador de atribuição de cópias, que pode ficar ainda mais complicado, especialmente no caso não trivial. É uma tonelada de caldeira que você não quer escrever para muitas classes, mas de qualquer maneira é obrigado a fazê-lo no C ++ 03:
struct T {
std::shared_ptr<int> b;
T(); // the usual definitions
T(const T&);
T& operator=(const T& src) {
if (this != &src) // not actually needed for this simple example
b = src.b; // non-trivial operation
return *this;
};
Esse é um caso simples, mas já é mais código do que você gostaria de ser forçado a escrever para um tipo tão simples como T
(especialmente quando lançamos as operações para o mix). Não podemos confiar em um corpo vazio que significa "preencher os padrões" porque o corpo vazio já é perfeitamente válido e tem um significado claro. De fato, se o corpo vazio fosse usado para indicar "preencher os padrões", não haveria maneira de criar explicitamente um construtor de cópia não operacional ou algo semelhante.
É novamente uma questão de consistência. O corpo vazio significa "não faça nada", mas para coisas como construtores de cópias você realmente não quer "não faça nada", mas "faça todas as coisas que você faria normalmente se não fosse suprimido". Por isso =default
. É necessário superar funções-membro suprimidas geradas pelo compilador, como copiar / mover construtores e operadores de atribuição. É então "óbvio" fazê-lo funcionar também para o construtor padrão.
Poderia ter sido bom tornar o construtor padrão com corpos vazios e os construtores triviais de membros / base também sejam considerados triviais, da mesma forma que teriam sido =default
se tornassem o código antigo mais ideal em alguns casos, mas a maioria dos códigos de baixo nível baseados em triviais construtores padrão para otimizações também contam com construtores de cópia triviais. Se você precisar "consertar" todos os seus construtores de cópias antigos, também não é muito difícil consertar todos os seus construtores padrão antigos. Também é muito mais claro e óbvio, usando um explícito =default
para denotar suas intenções.
Existem algumas outras coisas que as funções-membro geradas pelo compilador farão, que você também teria que fazer explicitamente alterações no suporte. O suporte constexpr
para construtores padrão é um exemplo. É mais fácil de usar mentalmente do =default
que ter que marcar funções com todas as outras palavras-chave especiais implícitas =default
e esse era um dos temas do C ++ 11: tornar a linguagem mais fácil. Ele ainda tem muitas verrugas e compromissos de compatibilidade traseira, mas é claro que é um grande passo à frente do C ++ 03 quando se trata de facilidade de uso.
= default
que faria a=0;
e não era! Eu tive que desistir disso : a(0)
. Ainda estou confuso sobre o quão útil = default
é essa, é sobre desempenho? vai quebrar em algum lugar se eu simplesmente não usar = default
? Eu tentei ler todas as respostas aqui comprar Eu sou novo em algumas coisas em c ++ e estou tendo muitos problemas para entendê-lo.
a=0
exemplo é devido ao comportamento de tipos triviais, que são um tópico separado (embora relacionado).
= default
e ainda conceder a
será =0
? de algum modo? você acha que eu poderia criar uma nova pergunta como "como ter um construtor = default
e conceder que os campos sejam inicializados corretamente?", btw Eu tive o problema em um struct
e não umclass
, eo aplicativo está sendo executado corretamente, mesmo não usando = default
, eu posso adicione uma estrutura mínima a essa pergunta, se ela for boa :) #
struct { int a = 0; };
Se você decidir que precisa de um construtor, poderá padronizá-lo, mas observe que o tipo não será trivial (o que é bom).
Devido à reprovação std::is_pod
e à sua alternativa std::is_trivial && std::is_standard_layout
, o trecho da resposta de @JosephMansfield se torna:
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() {}
};
int main() {
static_assert(std::is_trivial_v<X>, "X should be trivial");
static_assert(std::is_standard_layout_v<X>, "X should be standard layout");
static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}
Observe que o Y
layout ainda é padrão.
default
não é uma nova palavra-chave, é apenas um novo uso de uma palavra-chave já reservada.