Memanggil konstruktor dalam c ++ tanpa yang baru


142

Saya sering melihat bahwa orang membuat objek menggunakan C ++

Thing myThing("asdf");

Alih-alih ini:

Thing myThing = Thing("asdf");

Ini tampaknya berhasil (menggunakan gcc), setidaknya selama tidak ada template yang terlibat. Pertanyaan saya sekarang, apakah baris pertama benar dan jika demikian saya harus menggunakannya?


25
Entah bentuk tanpa baru.
Daniel Daranas

13
Bentuk kedua akan menggunakan copy constructor jadi tidak, mereka tidak setara.
Edward Strange

Saya bermain sedikit dengan itu, cara pertama tampaknya gagal kadang-kadang ketika template digunakan dengan konstruktor tanpa parameter ..
Nils

1
Ouh dan saya mendapat lencana "Pertanyaan Bagus" untuk itu, sungguh memalukan!
Nils

Jawaban:


153

Kedua garis itu sebenarnya benar tetapi melakukan hal-hal yang agak berbeda.

Baris pertama membuat objek baru di stack dengan memanggil konstruktor format Thing(const char*).

Yang kedua sedikit lebih rumit. Ini pada dasarnya melakukan hal berikut

  1. Buat objek bertipe Thingmenggunakan konstruktorThing(const char*)
  2. Buat objek bertipe Thingmenggunakan konstruktorThing(const Thing&)
  3. Panggil ~Thing()objek yang dibuat pada langkah # 1

7
Saya kira jenis tindakan ini dioptimalkan dan oleh karena itu, tidak berbeda secara signifikan dalam aspek kinerja.
M. Williams

14
Saya pikir langkah Anda tidak tepat. Thing myThing = Thing(...)tidak menggunakan operator penugasan, itu masih dikopi-dibangun seperti mengatakan Thing myThing(Thing(...)), dan tidak melibatkan bawaan-dibangun Thing(sunting: posting kemudian dikoreksi)
AshleysBrain

1
Jadi Anda dapat mengatakan bahwa baris kedua tidak benar, karena menghabiskan sumber daya tanpa alasan yang jelas. Tentu saja dimungkinkan bahwa pembuatan instance pertama disengaja untuk beberapa efek samping, tetapi itu bahkan lebih buruk (secara gaya).
MK.

3
Tidak, @Jared, tidak dijamin. Tetapi bahkan jika kompiler memilih untuk melakukan optimasi itu, copy constructor masih perlu diakses (yaitu, tidak dilindungi atau pribadi), bahkan jika itu tidak diimplementasikan atau dipanggil.
Rob Kennedy

3
Tampaknya salinan dapat dihapus bahkan jika konstruktor salinan memiliki efek samping - lihat jawaban saya: stackoverflow.com/questions/2722879/…
Douglas Leeder

31

Saya berasumsi dengan baris kedua yang Anda maksud:

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

yang akan menjadi cara standar untuk membuat objek dinamis baru (diperlukan untuk pengikatan dinamis dan polimorfisme) dan menyimpan alamatnya ke sebuah pointer. Kode Anda melakukan apa yang dijelaskan oleh JaredPar, yaitu membuat dua objek (satu melewati a const char*, yang lainnya melewati a const Thing&), dan kemudian memanggil destruktor ( ~Thing()) pada objek pertama ( const char*satu).

Sebaliknya, ini:

Thing thing("uiae");

membuat objek statis yang dihancurkan secara otomatis saat keluar dari cakupan saat ini.


1
Sayangnya, itu memang cara paling umum untuk membuat objek dinamis baru daripada menggunakan auto_ptr, unique_ptr, atau yang terkait.
Fred Nurk

3
Pertanyaan OP itu benar, jawaban ini sepenuhnya menyangkut masalah lain (lihat jawaban JaredPar)
Silmathoron

21

Kompilator mungkin mengoptimalkan bentuk kedua ke dalam bentuk pertama, tetapi tidak harus.

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

Output dari 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

Apa tujuan dari gips statis untuk membatalkan?
Stephen Cross

1
@Stephen Hindari peringatan tentang variabel yang tidak digunakan.
Douglas Leeder

10

Sederhananya, kedua garis membuat objek pada stack, bukan pada heap seperti halnya 'baru'. Baris kedua sebenarnya melibatkan panggilan kedua ke copy constructor, jadi itu harus dihindari (juga perlu diperbaiki seperti yang ditunjukkan dalam komentar). Anda harus menggunakan tumpukan untuk objek kecil sebanyak mungkin karena lebih cepat, namun jika objek Anda akan bertahan lebih lama dari tumpukan bingkai, maka itu jelas pilihan yang salah.


Bagi mereka yang tidak terbiasa dengan perbedaan antara objek instantiating pada stack sebagai lawan di heap (yang menggunakan yang baru dan tidak menggunakan yang baru ), inilah utas yang bagus.
edmqkk

2

Idealnya, kompiler akan mengoptimalkan yang kedua, tetapi itu tidak diperlukan. Yang pertama adalah cara terbaik. Namun, sangat penting untuk memahami perbedaan antara tumpukan dan tumpukan di C ++, karena Anda harus mengelola memori tumpukan Anda sendiri.


Bisakah kompiler menjamin bahwa copy constructor tidak memiliki efek samping (seperti I / O)?
Stephen Cross

@Stephen - tidak masalah jika copy constructor saya / O - lihat jawaban saya stackoverflow.com/questions/2722879/…
Douglas Leeder

Ok, saya mengerti, kompiler diperbolehkan untuk mengubah bentuk kedua menjadi yang pertama dan dengan demikian menghindari panggilan ke copy constructor.
Stephen Cross

2

Saya bermain sedikit dengan itu dan sintaks tampaknya agak aneh ketika konstruktor tidak mengambil argumen. Izinkan saya memberi contoh:

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

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

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

}

jadi hanya menulis Thing myThing tanpa tanda kurung sebenarnya memanggil konstruktor, sementara Thing myThing () membuat hal yang kompiler Anda ingin membuat pointer fungsi atau sesuatu ?? !!


6
Ini adalah ambiguitas sintaksis yang terkenal di C ++. Ketika Anda menulis "int rand ()", kompiler tidak dapat mengetahui apakah maksud Anda "membuat int dan menginisialisasi-awal" atau "menyatakan fungsi rand". Aturannya adalah bahwa ia memilih yang terakhir bila memungkinkan.
jpalecek

1
Dan ini, teman-teman, adalah yang paling menjengkelkan .
Marc.2377

2

Dalam menambahkan jawaban JaredPar

1-ctor biasa, 2nd-function-like-ctor dengan objek sementara.

Kompilasi sumber ini di suatu tempat di sini http://melpon.org/wandbox/ dengan kompiler yang berbeda

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

Dan Anda akan melihat hasilnya.

Dari ISO / IEC 14882 2003-10-15

8.5, bagian 12

Konstruksi ke-1, ke-2 Anda disebut inisialisasi langsung

12.1, bagian 13

Konversi tipe notasi fungsional (5.2.3) dapat digunakan untuk membuat objek baru dari tipenya. [Catatan: Sintaksnya terlihat seperti panggilan eksplisit dari konstruktor. ] ... Objek yang dibuat dengan cara ini tidak disebutkan namanya. [Catatan: 12.2 menjelaskan umur benda sementara. ] [Catatan: panggilan konstruktor eksplisit tidak menghasilkan nilai, lihat 3.10. ]


Di mana membaca tentang RVO:

12 Fungsi anggota khusus / 12.8 Menyalin objek kelas / Bagian 15

Ketika kriteria tertentu terpenuhi, implementasi diperbolehkan untuk menghilangkan konstruksi salinan objek kelas, bahkan jika konstruktor dan / atau destruktor untuk objek memiliki efek samping .

Matikan itu dengan flag compiler dari komentar untuk melihat perilaku copy-seperti itu)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.