Chamando construtores em c ++ sem novas

142

Eu sempre vi que as pessoas criam objetos em C ++ usando

Thing myThing("asdf");

Em vez disso:

Thing myThing = Thing("asdf");

Isso parece funcionar (usando o gcc), pelo menos enquanto não houver modelos envolvidos. Minha pergunta agora, a primeira linha está correta e, em caso afirmativo, devo usá-la?

Nils
fonte
25
Qualquer forma é sem novo.
Daniel Daranas
13
O segundo formulário usará o construtor de cópias, portanto não, eles não são equivalentes.
Edward Strange
Joguei um pouco com ele, a primeira forma parece falhar algumas vezes, quando os modelos são usados com construtores sem parâmetros ..
Nils
1
Ouh e eu recebemos o distintivo "Nice Question" por isso, que pena!
Nils

Respostas:

153

As duas linhas estão de fato corretas, mas fazem coisas sutilmente diferentes.

A primeira linha cria um novo objeto na pilha chamando um construtor do formato Thing(const char*).

O segundo é um pouco mais complexo. Essencialmente, faz o seguinte

  1. Crie um objeto do tipo Thingusando o construtorThing(const char*)
  2. Crie um objeto do tipo Thingusando o construtorThing(const Thing&)
  3. Chame ~Thing()o objeto criado na etapa # 1
JaredPar
fonte
7
Eu acho que esses tipos de ações são otimizados e, portanto, não diferem significativamente nos aspectos de desempenho.
M. Williams
14
Não acho que seus passos estejam certos. Thing myThing = Thing(...)não usa o operador de atribuição, é ainda cópia construído apenas como dizer Thing myThing(Thing(...)), e não envolve um construído-default Thing(edit: pós foi posteriormente corrigida)
AshleysBrain
1
Portanto, você pode dizer que a segunda linha está incorreta, porque desperdiça recursos sem motivo aparente. É claro que é possível que a criação da primeira instância seja intencional para alguns efeitos colaterais, mas isso é ainda pior (estilisticamente).
MK.
3
Não, @Jared, não é garantido. Mas mesmo que o compilador opte por executar essa otimização, o construtor de cópias ainda precisa estar acessível (ou seja, não protegido ou privado), mesmo que não seja implementado ou chamado.
Rob Kennedy
3
Parece que a cópia pode ser elidida mesmo que o construtor de cópia tem efeitos colaterais - veja minha resposta: stackoverflow.com/questions/2722879/...
Douglas Leeder
31

Suponho que com a segunda linha você realmente queira dizer:

Thing *thing = new Thing("uiae");

que seria a maneira padrão de criar novos objetos dinâmicos (necessários para ligação dinâmica e polimorfismo) e armazenar seu endereço em um ponteiro. Seu código faz o que JaredPar descreveu, ou seja, criar dois objetos (um passou a const char*, o outro passou a const Thing&) e, em seguida, chamar o destruidor ( ~Thing()) no primeiro objeto (oconst char* aquele).

Por outro lado, isso:

Thing thing("uiae");

cria um objeto estático que é destruído automaticamente ao sair do escopo atual.

knittl
fonte
1
Infelizmente, essa é realmente a maneira mais comum de criar novos objetos dinâmicos em vez de usar auto_ptr, unique_ptr ou relacionados.
precisa saber é o seguinte
3
A pergunta do OP foi correta, esta resposta diz respeito outra questão inteiramente (ver resposta da @ JaredPar)
Silmathoron
21

O compilador pode otimizar o segundo formulário para o primeiro, mas não precisa.

#include <iostream>

class A
{
    public:
        A() { std::cerr << "Empty constructor" << std::endl; }
        A(const A&) { std::cerr << "Copy constructor" << std::endl; }
        A(const char* str) { std::cerr << "char constructor: " << str << std::endl; }
        ~A() { std::cerr << "destructor" << std::endl; }
};

void direct()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void assignment()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a = A(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void prove_copy_constructor_is_called()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    A b = a;
    static_cast<void>(b); // avoid warnings about unused variables
}

int main()
{
    direct();
    assignment();
    prove_copy_constructor_is_called();
    return 0;
}

Saída do gcc 4.4:

TEST: direct
char constructor: direct
destructor

TEST: assignment
char constructor: assignment
destructor

TEST: prove_copy_constructor_is_called
char constructor: prove_copy_constructor_is_called
Copy constructor
destructor
destructor
Douglas Leeder
fonte
Qual é o propósito dos elencos estáticos para anular?
Stephen Cruz
1
@ Stephen Evite avisos sobre variáveis ​​não utilizadas.
Douglas Leeder
10

Simplesmente, ambas as linhas criam o objeto na pilha, em vez de no heap como 'new'. A segunda linha, na verdade, envolve uma segunda chamada para um construtor de cópias, portanto deve ser evitada (também precisa ser corrigida conforme indicado nos comentários). Você deve usar a pilha para objetos pequenos o máximo possível, pois é mais rápida; no entanto, se seus objetos sobreviverem por mais tempo que o quadro da pilha, é claramente a escolha errada.

Stephen Cross
fonte
Para aqueles que não estão familiarizados com a diferença entre instanciar objetos na pilha e não na pilha (que está usando novo e não novo ), aqui está um bom tópico.
Edmqkk 17/02/19
2

Idealmente, um compilador otimizaria o segundo, mas não é necessário. O primeiro é o melhor caminho. No entanto, é bastante crítico entender a distinção entre pilha e pilha no C ++, pois você deve gerenciar sua própria memória de pilha.

Cachorro
fonte
O compilador pode garantir que o construtor de cópias não tenha efeitos colaterais (como E / S)?
Stephen Cruz
@ Stephen - não importa se o construtor de cópia faz E / S - veja minha resposta stackoverflow.com/questions/2722879/…
Douglas Leeder
Ok, entendo, o compilador pode transformar o segundo formulário no primeiro e, assim, evita a chamada ao construtor de cópias.
Stephen Cruz
2

Eu brinquei um pouco com isso e a sintaxe parece bastante estranha quando um construtor não aceita argumentos. Deixe-me dar um exemplo:

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

Thing::Thing()
{
    cout << "Hi" << endl;
}

int main()
{
    //Thing myThing(); // Does not work
    Thing myThing; // Works

}

então apenas escrever Thing myThing sem colchetes realmente chama o construtor, enquanto Thing myThing () faz com que o compilador deseje criar um ponteiro de função ou algo assim ?? !!

Nils
fonte
6
Essa é uma ambiguidade sintática bem conhecida em C ++. Quando você escreve "int rand ()", o compilador não pode saber se você quer dizer "crie um int e inicialize-o por padrão" ou "declare a função rand". A regra é que ele escolhe o último sempre que possível.
precisa saber é o seguinte
1
E isso, pessoal, é a análise mais irritante .
Marc.2377
2

Em anexo a JaredPar resposta

1-Ctor usual, 2-Função-como-Ctor com objeto temporário.

Compile esta fonte em algum lugar aqui http://melpon.org/wandbox/ com diferentes compiladores

// turn off rvo for clang, gcc with '-fno-elide-constructors'

#include <stdio.h>
class Thing {
public:
    Thing(const char*){puts(__FUNCTION__ );}
    Thing(const Thing&){puts(__FUNCTION__ );}   
    ~Thing(){puts(__FUNCTION__);}
};
int main(int /*argc*/, const char** /*argv*/) {
    Thing myThing = Thing("asdf");
}

E você verá o resultado.

ISO / IEC 14882 2003-10-15

8.5, parte 12

Sua primeira e segunda construção são chamadas de inicialização direta

12.1, parte 13

Uma conversão de tipo de notação funcional (5.2.3) pode ser usada para criar novos objetos desse tipo. [Nota: A sintaxe se parece com uma chamada explícita do construtor. ] ... Um objeto criado dessa maneira não tem nome. [Nota: 12.2 descreve a vida útil dos objetos temporários. ] [Nota: chamadas explícitas ao construtor não produzem valores l, consulte 3.10. ]


Onde ler sobre o RVO:

12 Funções de membro especiais / 12.8 Copiando objetos de classe / Parte 15

Quando certos critérios são atendidos, uma implementação pode omitir a construção de cópia de um objeto de classe, mesmo se o construtor e / ou destruidor de cópia do objeto tiver efeitos colaterais .

Desative-o com o sinalizador do compilador do comentário para visualizar esse comportamento de cópia)

bruziuz
fonte