Yeni olmadan c ++ içinde kurucuları çağırmak


142

Sıklıkla insanların C ++ kullanarak nesneler oluşturduğunu gördüm

Thing myThing("asdf");

Bunun yerine:

Thing myThing = Thing("asdf");

Bu, en azından ilgili şablon olmadığı sürece (gcc kullanarak) işe yarıyor gibi görünüyor. Şimdi sorum, ilk satır doğru mu ve eğer öyleyse kullanmalıyım?


25
Her iki form da yenidir.
Daniel Daranas

13
İkinci form kopyalama yapıcısını kullanır, bu nedenle hayır, eşdeğer değildir.
Edward Strange

Onunla biraz oynadım, şablonlar parametresiz yapıcılar ile kullanıldığında ilk yol bazen başarısız gibi görünüyor ..
Nils

1
Ouh ve ben bunun için "Güzel Soru" rozeti aldık, ne yazık!
Nils

Yanıtlar:


153

Her iki çizgi de doğrudur, ancak farklı şeyler yaparlar.

İlk satır, biçimin bir yapıcısını çağırarak yığın üzerinde yeni bir nesne oluşturur Thing(const char*).

İkincisi biraz daha karmaşık. Aslında aşağıdakileri yapar

  1. Yapıcıyı Thingkullanarak tür nesnesi oluşturmaThing(const char*)
  2. Yapıcıyı Thingkullanarak tür nesnesi oluşturmaThing(const Thing&)
  3. ~Thing()1. adımda oluşturulan nesneyi çağırın

7
Sanırım bu tür eylemler optimize edildi ve bu nedenle performans yönlerinde önemli ölçüde farklı değil.
M. Williams,

14
Adımlarının doğru olduğunu düşünmüyorum. Thing myThing = Thing(...)atama operatörünü kullanmaz, yine de tıpkı söylemek gibi kopya Thing myThing(Thing(...))Thing
yapılıdır

1
Böylece, ikinci satırın yanlış olduğunu söyleyebilirsiniz, çünkü belirgin bir sebep olmadan kaynakları boşa harcar. Tabii ki, ilk örneği oluşturmanın bazı yan etkiler için kasıtlı olması mümkündür, ancak bu daha da kötü (stilistik olarak).
MK.

3
Hayır, @Cared, garanti edilmez. Ancak derleyici bu optimizasyonu gerçekleştirmeyi seçse bile, kopya oluşturucuya uygulanmamış veya çağrılmamış olsa bile yine de erişilebilir olması gerekir (yani korumalı veya özel değil).
Rob Kennedy

3
Kopya yapıcısının yan etkileri olsa bile kopya seçilebilir - cevabımı görün: stackoverflow.com/questions/2722879/…
Douglas Leeder

31

Sanırım ikinci satırla aslında demek istediğin:

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

yeni dinamik nesneler (dinamik bağlama ve polimorfizm için gerekli) oluşturmanın ve adreslerini bir göstergeye kaydetmenin standart yolu olurdu . Kodunuz JaredPar'ın tanımladığı şeyi yapar, yani iki nesne oluşturmak (biri geçti const char*, diğeri geçti a const Thing&) ve sonra ~Thing()ilk nesne (bir) üzerinde yıkıcı ( ) çağırmak const char*.

Aksine, bu:

Thing thing("uiae");

geçerli kapsamdan çıkıldığında otomatik olarak yok edilen statik bir nesne oluşturur.


1
Ne yazık ki, auto_ptr, unique_ptr veya ilgili kullanmak yerine yeni dinamik nesneler oluşturmanın en yaygın yolu budur.
Fred Nurk

3
OP'nin sorusu doğruydu, bu cevap tamamen başka bir konuyla ilgili (bkz. @ JaredPar'ın cevabı)
Silmathoron

21

Derleyici ikinci formu ilk forma iyi optimize edebilir, ancak zorunlu değildir.

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

Gcc 4.4 çıktısı:

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

Statik dökümlerin geçersiz kılınmasının amacı nedir?
Stephen Cross

1
@Stephen Kullanılmayan değişkenlerle ilgili uyarılardan kaçının.
Douglas Leeder

10

Oldukça basit bir şekilde, her iki satır da nesneyi 'yeni' gibi yığın yerine yığın üzerinde oluşturur. İkinci satır aslında bir kopya oluşturucuya ikinci bir çağrı içerir, bu yüzden bundan kaçınılmalıdır (ayrıca yorumlarda belirtildiği gibi düzeltilmesi gerekir). Yığını, daha hızlı olduğu için mümkün olduğunca küçük nesneler için kullanmalısınız, ancak nesneleriniz yığın çerçevesinden daha uzun süre hayatta kalacaksa, o zaman açıkça yanlış seçimdir.


Yığının aksine ( yeni kullanan ve yeni olmayan ) yığındaki örnekleme nesneleri arasındaki farkı bilmeyenler için işte iyi bir iş parçacığı.
edmqkk

2

İdeal olarak, bir derleyici ikinciyi optimize eder, ancak gerekli değildir. Birincisi en iyi yol. Ancak, C ++ 'da yığın ve yığın arasındaki farkı anlamak oldukça önemlidir, sinüs kendi yığın belleğinizi yönetmeniz gerekir.


Derleyici, kopya oluşturucunun yan etkileri olmadığını (G / Ç gibi) garanti edebilir mi?
Stephen Cross

@Stephen - kopya oluşturucunun I / O yapması önemli değil - cevabımı görün stackoverflow.com/questions/2722879/…
Douglas Leeder

Tamam, görüyorum, derleyici ikinci formu birinciye çevirebilir ve böylece kopya kurucuya çağrı yapılmasını önler.
Stephen Cross

2

Onunla biraz oynadım ve bir yapıcı argüman almadığında sözdizimi oldukça garip görünüyor. Bir örnek vereyim:

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

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

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

}

Thing myThing () bir işlev işaretçisi ya da bir şey oluşturmak istediğiniz derleyici şey yaparken ??


6
Bu, C ++ 'da iyi bilinen bir sözdizimsel belirsizliktir. "İnt rand ()" yazdığınızda, derleyici "int oluştur ve varsayılan olarak başlat" veya "işlev rand bildir" anlamına mı geldiğini bilemez. Kural, mümkün olduğunda ikincisini seçmesidir.
jpalecek


2

JaredPar cevabına ek olarak

1-olağan ctor, geçici nesneli 2. fonksiyon benzeri ctor.

Bu kaynağı burada bir yerde derleyin http://melpon.org/wandbox/ farklı derleyicilerle

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

Ve sonucu göreceksiniz.

ISO / IEC 14882 2003-10-15'ten

8.5, bölüm 12

1. ve 2. yapılarınıza doğrudan başlatma adı verilir

12.1, bölüm 13

Kendi türünde yeni nesneler oluşturmak için işlevsel bir gösterim türü dönüştürme (5.2.3) kullanılabilir. [Not: Sözdizimi kurucunun açık bir çağrısına benziyor. ] ... Bu şekilde oluşturulan bir nesnenin adı yoktur. [Not: 12.2, geçici nesnelerin ömrünü açıklar. ] [Not: açık kurucu çağrıları değer vermez, bkz. 3.10. ]


RVO hakkında nereden okunabilir:

12 Özel üye işlevleri / 12.8 Sınıf nesnelerini kopyalama / Bölüm 15

Belirli ölçütler karşılandığında, nesnenin kopyalama yapıcısı ve / veya yıkıcısının yan etkileri olsa bile bir uygulamanın sınıf nesnesinin kopya yapısını atlamasına izin verilir .

Bu tür kopya davranışlarını görüntülemek için yorumdaki derleyici bayrağıyla kapatın)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.