C ++ 'da garantili geçici ömür?


103

C ++, bir işlev çağrısı içinde oluşturulan ancak bir parametre olarak kullanılmayan geçici bir değişkenin yaşam süresi için bir garanti sağlıyor mu? İşte örnek bir sınıf:

class StringBuffer
{
public:
    StringBuffer(std::string & str) : m_str(str)
    {
        m_buffer.push_back(0);
    }
    ~StringBuffer()
    {
        m_str = &m_buffer[0];
    }
    char * Size(int maxlength)
    {
        m_buffer.resize(maxlength + 1, 0);
        return &m_buffer[0];
    }
private:
    std::string & m_str;
    std::vector<char> m_buffer;
};

Ve işte bunu nasıl kullanacağınız:

// this is from a crusty old API that can't be changed
void GetString(char * str, int maxlength);

std::string mystring;
GetString(StringBuffer(mystring).Size(MAXLEN), MAXLEN);

Geçici StringBuffer nesnesinin yıkıcısı ne zaman çağrılacak? Bu mu:

  • GetString'i aramadan önce?
  • GetString döndükten sonra?
  • Derleyiciye bağlı mı?

C ++ 'nın, yerel bir geçici değişkenin kendisine bir başvuru olduğu sürece geçerli olacağını garanti ettiğini biliyorum - bu, bir üye değişkene başvuru olduğunda ana nesneler için geçerli midir?

Teşekkürler.


neden devralmayalım ve aşırı yüklemeyelim veya küresel bir işlev yapmayalım? Daha temiz olurdum ve bir üyeyi aramak için bir sınıf oluşturmak zorunda kalmazsınız.
Jacek Ławrynowicz

1
Bunu kullanacağız varsa, çağırmalıdır m_str.reserve(maxlength)içinde char * Size(int maxlength)aksi yıkıcı atabilseydin.
Mankarse

Yanıtlar:


109

Bu tür geçiciler için yıkıcı, tam ifadenin sonunda çağrılır. Bu, başka bir ifadenin parçası olmayan en dış ifade. Bu, fonksiyon döndükten ve değer değerlendirildikten sonra sizin durumunuzdur. Yani, her şey yolunda gidecek.

Aslında ifade şablonlarının çalışmasını sağlayan şey budur: Bu tür geçicilere referansları bir ifadede tutabilirler.

e = a + b * c / d

Çünkü her geçici, ifadeye kadar sürer

x = y

Tamamen değerlendirilir. 12.2 Temporary objectsStandart'ta oldukça kısaca açıklanmıştır .


3
Standardın bir kopyasını hiçbir zaman tam olarak alamadım. Bunu bir öncelik haline getirmeliyim.
Mark Ransom

2
@JohannesSchaub: Bu durumda "tam ifade" printf("%s", strdup(std::string("$$$").c_str()) );nedir? Yani strdup(std::string("$$$").c_str())tam ifade olarak alınırsa, strdupgören işaretçi geçerlidir . Eğer std::string("$$$").c_str()tam bir ifadesidir ardından işaretçi strdupgörür ise geçersiz ! Bu örneğe dayanarak biraz daha açıklayabilir misiniz?
Grim Fandango

2
@GrimFandango AIUI sizin bütününüz printftam ifadedir. Bu nedenle strdupgereksiz bir bellek sızıntısıdır - c_str()doğrudan yazdırmasına izin verebilirsiniz .
Josh Stone

1
"Bu tür geçiciler" - Bu ne tür? Geçici olduğumun "bu tür" geçici olup olmadığını nasıl anlarım?
RM

@RM yeterince adil. Soruda yapıldığı gibi "bir işlev bağımsız değişkeni içinde oluşturduklarınız" demek istedim.
Johannes Schaub -

18

litb'nin cevabı doğrudur. Geçici nesnenin ömrü (rvalue olarak da bilinir) ifadeye bağlıdır ve geçici nesnenin yıkıcısı tam ifadenin sonunda çağrılır ve StringBuffer'daki yıkıcı çağrıldığında m_buffer'daki yıkıcı da çağrıldı, ancak m_str üzerindeki yıkıcı bir başvuru olduğu için değil.

C ++ 0x'in her şeyi biraz değiştirdiğine dikkat edin çünkü rvalue referansları ekler ve semantiği taşır. Esasen bir rvalue referans parametresi kullanarak (&& ile işaretlenmiştir) rvalue'yu işleve 'taşıyabilirim' (kopyalamak yerine) ve rvalue'nun ömrü ifadeye değil, içine geçtiği nesneye bağlanabilir. MSVC ekibinden bu konuyu detaylı bir şekilde anlatan gerçekten iyi bir blog yazısı var ve insanları okumaya teşvik ediyorum.

Rvalue'ları taşımak için pedagojik örnek geçici dizelerdir ve bir yapıcıda atama göstereceğim. Bir dize üye değişkeni içeren bir sınıf MyType'a sahipsem, yapıcıda şu şekilde bir rvalue ile başlatılabilir:

class MyType{
   const std::string m_name;
public:
   MyType(const std::string&& name):m_name(name){};
}

Bu güzel çünkü bu sınıfın bir örneğini geçici bir nesneyle bildirdiğimde:

void foo(){
    MyType instance("hello");
}

geçici nesneyi kopyalamaktan ve yok etmekten kaçınmamız ve "merhaba" doğrudan sahip olan sınıfın üye değişkeninin içine yerleştirilmesidir. Nesne bir 'dizgiden' daha ağırsa, fazladan kopya ve yıkıcı çağrısı önemli olabilir.


1
Taşımanın çalışması için, const'ı bırakmanız ve MyType (std :: string && name) gibi std :: move kullanmanız gerektiğini düşünüyorum: m_name (std :: move (name)) {}
gast128


3

StringBuffer, GetString kapsamındadır. GetString'in kapsamının sonunda (yani geri döndüğünde) yok edilmelidir. Ayrıca, C ++ 'nın, referans olduğu sürece bir değişkenin var olacağını garanti edeceğine inanmıyorum.

Aşağıdakiler derlenmelidir:

Object* obj = new Object;
Object& ref = &(*obj);
delete obj;

Garantiyi abarttığımı düşünüyorum - sadece yerel geçiciler için. Ama var.
Mark Ransom

Soruyu düzenledim. Şimdiye kadar verilen cevaplara göre, yine de tartışmalı bir nokta gibi görünüyor.
Mark Ransom

Hala düzenlemenizin doğru olduğunu düşünmüyorum: Object & obj = GetObj (); Object & GetObj () {return & Object (); } // kötü - sarkan bir referans bırakır.
BigSandwich

1
Açıkçası kendimi açıklamakta kötü bir iş yapıyorum ve% 100 de anlamayabilirim. İnformit.com/guides/content.aspx?g=cplusplus&seqNum=198 sayfasına bakın - bu benim orijinal sorumu da açıklıyor ve cevaplıyor.
Mark Ransom

1
Bağlantı için teşekkürler, şimdi mantıklı.
BigSandwich

3

Neredeyse tamamen aynı sınıfta yazdım:

template <class C>
class _StringBuffer
{
    typename std::basic_string<C> &m_str;
    typename std::vector<C> m_buffer;

public:
    _StringBuffer(std::basic_string<C> &str, size_t nSize)
        : m_str(str), m_buffer(nSize + 1) { get()[nSize] = (C)0; }

    ~_StringBuffer()
        { commit(); }

    C *get()
        { return &(m_buffer[0]); }

    operator C *()
        { return get(); }

    void commit()
    {
        if (m_buffer.size() != 0)
        {
            size_t l = std::char_traits<C>::length(get());
            m_str.assign(get(), l);    
            m_buffer.resize(0);
        }
    }

    void abort()
        { m_buffer.resize(0); }
};

template <class C>
inline _StringBuffer<C> StringBuffer(typename std::basic_string<C> &str, size_t nSize)
    { return _StringBuffer<C>(str, nSize); }

Standarttan önce her derleyici bunu farklı şekilde yapıyordu. C ++ için eski Açıklamalı Referans Kılavuzunun kapsamın sonunda geçici dosyaların temizlenmesi gerektiğini belirttiğine inanıyorum, bu nedenle bazı derleyiciler bunu yaptı. 2003 gibi geç bir tarihte, bu davranışın Sun'ın Forte C ++ derleyicisinde hala varsayılan olarak var olduğunu buldum, bu yüzden StringBuffer çalışmadı. Ancak mevcut herhangi bir derleyici hala o kadar bozuksa şaşırırdım.


Ne kadar benzerler! Uyarı için teşekkürler - deneyeceğim ilk yer, standartlara uygunluğu ile bilinmeyen VC ++ 6'dır. Dikkatlice izleyeceğim.
Mark Ransom

Sınıfı orijinal olarak VC ++ 6'da yazardım, bu yüzden sorun olmamalı.
Daniel Earwicker
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.