保证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 ++保证只要有对它的引用,局部临时变量就将有效-当引用成员变量时,这是否适用于父对象?

谢谢。


为什么不继承和重载或创建全局函数?我会更清洁,您将不必创建类ony来调用成员。
JacekŁawrynowicz09年

1
如果你要使用它,你应该叫m_str.reserve(maxlength)char * Size(int maxlength),否则,析构函数可以抛出。
Mankarse 2012年

Answers:


109

这种临时对象的析构函数在全表达式的结尾被调用。那是最外部的表达式,它不属于任何其他表达式。在您的情况下,就是在函数返回并评估值之后。因此,一切正常。

实际上,这是使表达式模板起作用的原因:它们可以在诸如

e = a + b * c / d

因为每个临时都会持续到表达式

x = y

被完全评估。12.2 Temporary objects在标准中对此进行了简洁的描述。


3
我从来没有完全了解过该标准的副本。我应该优先考虑。
Mark Ransom

2
@JohannesSchaub:在这种情况下,什么是“完整表达式”:printf("%s", strdup(std::string("$$$").c_str()) );我的意思是,如果strdup(std::string("$$$").c_str())将其作为完整表达式,则strdup看到的指针是有效的。如果std::string("$$$").c_str()是完整表达式,则strdup看到的指针无效!您能否根据这个例子进一步解释一下?
Grim Fandango 2014年

2
@GrimFandango AIUI printf就是完整的表达。因此,这strdup是不必要的内存泄漏-您可以直接将其打印出来c_str()
乔什·斯通

1
“那种临时工”-那是什么?我怎么知道我的临时工是否是“那种”临时工?
RM

@RM足够公平。我的意思是“在函数参数中创建的变量”,就像在问题中所做的那样。
Johannes Schaub-litb

18

小人的答案是准确的。临时对象(也称为右值)的生命周期与表达式相关联,并且在完整表达式的末尾调用临时对象的析构函数,并且当调用StringBuffer上的析构函数时,m_buffer上的析构函数也将为调用,但不是m_str上的析构函数,因为它是一个引用。

请注意,C ++ 0x只是更改了一点,因为它添加了右值引用并移动了语义。本质上,通过使用右值引用参数(用&&表示),我可以将右值“移动”到函数中(而不是复制它),并且右值的生存期可以绑定到它要移动到的对象,而不是表达式。MSVC团队有一篇非常不错的博客文章,其中详细介绍了这一过程,我鼓励人们阅读它。

移动右值的教学示例是临时字符串,我将在构造函数中显示赋值。如果我有一个包含字符串成员变量的MyType类,则可以使用构造函数中的右值对其进行初始化,如下所示:

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

这很好,因为当我声明带有临时对象的此类的实例时:

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

发生的事情是我们避免复制和破坏临时对象,并且“ hello”直接放置在拥有类实例的成员变量内。如果对象的重量比“字符串”重,则额外的复制和析构函数调用可能很重要。


1
为了使移动有效,我认为您需要删除const并使用Myd(std :: string && name)的std :: move:m_name(std :: move(name)){}
gast128


3

StringBuffer在GetString的范围内。它应该在GetString范围的末尾(即返回时)被销毁。而且,我不相信C ++会保证只要有引用,变量就将存在。

以下应编译:

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

我认为我夸大了保证-仅适用于本地临时人员。但是它确实存在。
Mark Ransom

我已经编辑了问题。根据到目前为止提供的答案,这似乎是有争议的。
Mark Ransom

我仍然认为您的编辑不正确:Object&obj = GetObj(); Object&GetObj(){return&Object(); } //坏-将留下悬空的引用。
09年

1
我显然在解释自己方面做得不好,而且我可能也不是完全理解100%。请查看notifyit.com/guides/content.aspx?g=cplusplus&seqNum=198-它也解释并回答了我的原始问题。
Mark Ransom

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年末,我发现默认情况下Sun的Forte C ++编译器仍然存在该行为,因此StringBuffer不起作用。但是,如果任何当前的编译器仍然被破坏,我会感到惊讶。


怪异的相似之处!感谢您的警告-我将首先尝试的是VC ++ 6,它并不符合其标准。我会仔细看的。
Mark Ransom

我本来会在VC ++ 6上编写该类的,所以这应该不是问题。
丹尼尔·厄威克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.