Гарантійний термін служби тимчасово в C ++?


103

Чи надає C ++ гарантію на час дії тимчасової змінної, яка створюється в межах виклику функції, але не використовується як параметр? Ось приклад класу:

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

А ось як би ви його використовували:

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

Коли буде викликаний деструктор тимчасового об’єкта StringBuffer? Є це:

  • До дзвінка до GetString?
  • Після повернення GetString?
  • Компілятор залежить?

Я знаю, що C ++ гарантує, що локальна тимчасова змінна буде дійсною, поки на неї є посилання - чи це стосується батьківських об'єктів, коли є посилання на змінну члена?

Дякую.


чому б не успадкувати і не перевантажити або зробити глобальну функцію? я був би чистішим, і вам не потрібно було б створювати клас, щоб зателефонувати члену.
Яцек Ławrynowicz

1
Якщо ви збираєтеся використовувати це, ви повинні зателефонувати m_str.reserve(maxlength)в char * Size(int maxlength)іншому випадку деструктор може кинути.
Манкарсе

Відповіді:


109

Деструктор для такого роду тимчасових викликів називається в кінці повного виразу. Це самий зовнішній вираз, який не є частиною жодного іншого виразу. Це у вашому випадку після повернення функції та оцінювання значення. Отже, все буде добре.

Це справді те, що змушує працювати шаблони виразів: вони можуть зберігати посилання на такий тип тимчасових виразів, як вираз

e = a + b * c / d

Тому що кожна тимчасова триватиме до виразу

x = y

Оцінюється повністю. Це досить стисло описано в 12.2 Temporary objectsСтандарті.


3
Мені ніколи не доходило, щоб отримати копію стандарту. Я повинен зробити це пріоритетним.
Марк Рансом

2
@JohannesSchaub: Що таке "повний вираз" у цьому випадку: printf("%s", strdup(std::string("$$$").c_str()) );я маю на увазі, якщо strdup(std::string("$$$").c_str())його прийняти за повний вираз, то покажчик, який strdupбачить, є дійсним . Якщо std::string("$$$").c_str()є повним виразом, то покажчик, який strdupбачить, недійсний ! Не могли б ви пояснити трохи детальніше на цьому прикладі?
Грим Фанданго

2
@GrimFandango AIUI ваш printfповний вираз. Таким чином strdup, це непотрібний витік пам'яті - ви можете просто дозволити c_str()безпосередньо друкувати .
Джош Стоун

1
"Такого роду тимчасові" - Що це таке? Як я можу визначити, чи є мій тимчасовий "такий тип" тимчасовим?
RM

@RM досить справедливо. Я мав на увазі "ті, які ви створюєте в аргументі функції", як це робиться в питанні.
Йоханнес Шауб - ліб

18

Відповідь лампа точна. Термін експлуатації тимчасового об'єкта (також відомий як rvalue) прив'язаний до виразу, і деструктор тимчасового об'єкта викликається в кінці повного виразу, і коли виклик деструктора на StringBuffer, деструктор на m_buffer також буде викликається, але не деструктор на m_str, оскільки це посилання.

Зауважте, що C ++ 0x трохи змінює речі, оскільки додає посилання на рецензію та переміщує семантику. По суті, використовуючи опорний параметр rvalue (позначений &&), я можу 'перемістити' rvalue у функцію (замість її копіювання), і час життя rvalue може бути прив’язаний до об'єкта, в який він переміщується, а не до виразу. Є справді хороша публікація в блозі від команди MSVC, яка детально розглядає це питання, і я закликаю людей читати його.

Педагогічний приклад переміщення rvalue's - це тимчасові рядки, і я покажу призначення в конструкторі. Якщо у мене є клас MyType, який містить змінну елемента рядка, його можна ініціалізувати з rvalue в конструкторі так:

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

Це добре, тому що коли я оголошую екземпляр цього класу тимчасовим об’єктом:

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

що трапляється, це те, що ми уникаємо копіювання та знищення тимчасового об’єкта, а "привіт" розміщується безпосередньо всередині змінної члена екземпляра класу, що володіє. Якщо об'єкт має більшу вагу, ніж "рядок", то додатковий виклик копії та деструктора може бути значним.


1
Щоб зробити переміщення робочим, я думаю, вам потрібно скинути const і використовувати std :: move як MyType (std :: string && name): m_name (std :: move (name)) {}
gast128


3

StringBuffer входить до сфери використання GetString. Він повинен бути знищений в кінці області GetString (тобто коли він повертається). Крім того, я не вірю, що C ++ гарантує існування змінної до тих пір, поки є посилання.

Слід скласти наступне:

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

Я думаю, що я завищив гарантію - це лише для місцевих тимчасів. Але вона існує.
Марк Рансом

Я відредагував питання. Виходячи з відповідей, що надаються дотепер, здається, що це суперечка.
Марк Рансом

Я все ще не вважаю, що ваше редагування є правильним: Object & obj = GetObj (); Object & GetObj () {return & Object (); } // погано - залишить звисаючу посилання.
BigSandwich

1
Я, очевидно, роблю погану роботу з пояснення себе, і я, можливо, не розумію і на 100%. Подивіться на informit.com/guides/content.aspx?g=cplusplus&seqNum=198 - це пояснює і відповідає на моє первісне запитання.
Марк Викуп 11

1
Дякую, за посилання, яке зараз має сенс.
BigSandwich

3

Я написав майже такий самий клас:

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

До стандарту кожен компілятор робив це по-різному. Я вважаю, що в старому довідковому посібнику з анотацією для C ++ було вказано, що тимчасові журнали повинні очищатись в кінці області, тому деякі компілятори це зробили. Ще в 2003 році я виявив, що поведінка все ще існує за замовчуванням у компіляторі Forte C ++ Sun, тому StringBuffer не працює. Але я був би здивований, якби якийсь поточний компілятор все-таки був таким зламаним.


Моторошні, наскільки вони схожі! Дякую за попередження - перше місце, що я спробую, це VC ++ 6, який не відомий своєю відповідністю стандартам. Буду уважно спостерігати.
Марк Рансом

Я б написав клас спочатку на VC ++ 6, тому це не повинно бути проблемою.
Daniel Earwicker
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.