Wywołanie konstruktorów w C ++ bez nowego


142

Często widziałem, że ludzie tworzą obiekty w C ++ używając

Thing myThing("asdf");

Zamiast tego:

Thing myThing = Thing("asdf");

Wydaje się, że to działa (używając gcc), przynajmniej tak długo, jak długo nie ma żadnych szablonów. Moje pytanie, czy pierwsza linia jest poprawna, a jeśli tak, to powinienem jej użyć?


25
Każda forma jest bez nowej.
Daniel Daranas

13
Druga forma będzie używać konstruktora kopiującego, więc nie, nie są one równoważne.
Edward Strange

Trochę się bawiłem, pierwszy sposób wydaje się czasami zawodzić, gdy szablony są używane z konstruktorami bez parametrów ..
Nils

1
Och i dostałem za to odznakę „Niezłe pytanie”, co za wstyd!
Nils,

Odpowiedzi:


153

Obie wersety są w rzeczywistości poprawne, ale robią nieco inne rzeczy.

Pierwsza linia tworzy nowy obiekt na stosie przez wywołanie konstruktora formatu Thing(const char*).

Drugi jest nieco bardziej złożony. Zasadniczo wykonuje następujące czynności

  1. Utwórz obiekt typu Thingprzy użyciu konstruktoraThing(const char*)
  2. Utwórz obiekt typu Thingprzy użyciu konstruktoraThing(const Thing&)
  3. Wywołaj ~Thing()obiekt utworzony w kroku 1

7
Myślę, że tego typu działania są zoptymalizowane i dlatego nie różnią się znacząco pod względem wydajności.
M. Williams

14
Nie sądzę, że twoje kroki są właściwe. Thing myThing = Thing(...)nie używa operator przypisania, to nadal kopiowaniem skonstruowana tak jakby powiedzieć Thing myThing(Thing(...)), i nie wymaga domyślnej skonstruowany Thing(edit: post został następnie skorygowany)
AshleysBrain

1
Możesz więc powiedzieć, że druga linia jest nieprawidłowa, ponieważ marnuje zasoby bez wyraźnego powodu. Oczywiście możliwe jest, że utworzenie pierwszej instancji jest celowe ze względu na pewne skutki uboczne, ale to jeszcze gorzej (stylistycznie).
MK.

3
Nie, @Jared, to nie jest gwarantowane. Ale nawet jeśli kompilator zdecyduje się przeprowadzić tę optymalizację, konstruktor kopiujący nadal musi być dostępny (tj. Nie chroniony ani prywatny), nawet jeśli nie jest zaimplementowany lub wywołany.
Rob Kennedy

3
Wygląda na to, że kopię można usunąć, nawet jeśli konstruktor kopiujący ma efekty uboczne - zobacz moją odpowiedź: stackoverflow.com/questions/2722879/ ...
Douglas Leeder

31

Zakładam, że drugą linią masz na myśli:

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

który byłby standardowym sposobem tworzenia nowych obiektów dynamicznych (niezbędnych do dynamicznego wiązania i polimorfizmu) i przechowywania ich adresu we wskaźniku. Twój kod robi to, co opisał JaredPar, mianowicie tworzy dwa obiekty (jeden przeszedł a const char*, drugi przeszedł a const Thing&), a następnie wywołuje destruktor ( ~Thing()) na pierwszym obiekcie (tym const char*jednym).

Z kolei to:

Thing thing("uiae");

tworzy obiekt statyczny, który jest automatycznie niszczony po wyjściu z bieżącego zakresu.


1
Niestety, jest to rzeczywiście najczęstszy sposób tworzenia nowych obiektów dynamicznych zamiast używania auto_ptr, unique_ptr lub related.
Fred Nurk,

3
Pytanie OP było poprawne, ta odpowiedź dotyczy całkowicie innego problemu (patrz odpowiedź @ JaredPar)
Silmathoron

21

Kompilator może dobrze zoptymalizować drugi formularz do pierwszego formularza, ale nie musi.

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

Wyjście z 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

Jaki jest cel unieważnienia rzutów statycznych?
Stephen Cross

1
@Stephen Unikaj ostrzeżeń o nieużywanych zmiennych.
Douglas Leeder

10

Po prostu obie linie tworzą obiekt na stosie, a nie na stercie, jak robi to „nowy”. Druga linia faktycznie zawiera drugie wywołanie konstruktora kopiującego, więc należy tego unikać (należy to również poprawić, jak wskazano w komentarzach). Powinieneś używać stosu do małych obiektów tak często, jak to możliwe, ponieważ jest szybszy, jednak jeśli twoje obiekty mają przetrwać dłużej niż ramka stosu, jest to zdecydowanie zły wybór.


Dla tych, którzy nie są zaznajomieni z różnicą między tworzeniem instancji obiektów na stosie a tworzeniem instancji na stercie (czyli przy użyciu new i nie nowego ), oto dobry wątek.
edmqkk

2

Idealnie byłoby, gdyby kompilator zoptymalizował drugą, ale nie jest to wymagane. Pierwszy to najlepszy sposób. Jednak bardzo ważne jest zrozumienie różnicy między stosem a stertą w C ++, bez względu na to, że musisz zarządzać własną pamięcią stosu.


Czy kompilator może zagwarantować, że konstruktor kopiujący nie ma skutków ubocznych (takich jak operacje we / wy)?
Stephen Cross

@Stephen - nie ma znaczenia, czy konstruktor kopiujący wykonuje operacje we / wy - zobacz moją odpowiedź stackoverflow.com/questions/2722879/ ...
Douglas Leeder

Ok, widzę, kompilator może zamienić drugi formularz na pierwszy i tym samym unika wywołania konstruktora kopiującego.
Stephen Cross

2

Trochę się tym bawiłem i składnia wydaje się być dość dziwna, gdy konstruktor nie przyjmuje argumentów. Podam przykład:

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

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

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

}

więc samo napisanie Thing myThing bez nawiasów faktycznie wywołuje konstruktor, podczas gdy Thing myThing () sprawia, że ​​kompilator, który chcesz utworzyć, jest wskaźnikiem funkcji lub czymś podobnym ?? !!


6
Jest to dobrze znana niejednoznaczność składniowa w C ++. Kiedy piszesz „int rand ()”, kompilator nie może wiedzieć, czy masz na myśli „utwórz int i domyślną inicjalizację”, czy „zadeklaruj funkcję rand”. Zasada jest taka, że ​​w miarę możliwości wybiera to drugie.
jpalecek

1
A to, ludzie, jest najbardziej irytujące .
Marc 2377

2

W uzupełnieniu do odpowiedzi JaredPar

1-zwykły ctor, 2-funkcyjny-podobny-ctor z tymczasowym obiektem.

Skompiluj to źródło gdzieś tutaj http://melpon.org/wandbox/ z różnymi kompilatorami

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

I zobaczysz wynik.

Z ISO / IEC 14882 2003-10-15

8.5, część 12

Twoja pierwsza i druga konstrukcja to inicjalizacja bezpośrednia

12.1, część 13

Funkcjonalna konwersja typu notacji (5.2.3) może być użyta do tworzenia nowych obiektów tego typu. [Uwaga: składnia wygląda jak jawne wywołanie konstruktora. ] ... Utworzony w ten sposób obiekt nie ma nazwy. [Uwaga: 12.2 opisuje czas życia obiektów tymczasowych. ] [Uwaga: jawne wywołania konstruktora nie dają lvalues, patrz 3.10. ]


Gdzie poczytać o RVO:

12 Specjalne funkcje składowe / 12.8 Kopiowanie obiektów klas / Część 15

Gdy spełnione są określone kryteria, implementacja może pominąć tworzenie kopii obiektu klasy, nawet jeśli konstruktor kopiujący i / lub destruktor obiektu mają efekty uboczne .

Wyłącz to flagą kompilatora z komentarza, aby zobaczyć takie zachowanie kopiowania)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.