2009-02-25 90 views
85

C++是否为在函数调用中创建但未用作参数的临时变量的生命周期提供保证?下面是一个例子类:在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++保证只要有一个引用它的本地临时变量就会有效 - 当这个变量有一个引用时它是否适用于父对象?

谢谢。

+0

为什么不继承和重载或创建一个全局函数?我会更清洁,你不必创建一个班级打电话给成员。 – 2009-03-11 08:25:09

+1

如果你打算使用它,你应该在`char * Size(int maxlength)`中调用`m_str.reserve(maxlength)`,否则析构函数会抛出。 – Mankarse 2012-03-18 02:22:18

回答

93

该类临时对象的析构函数在完整表达式的末尾被调用。这是最外层的表达,不是任何其他表达的一部分。在你的情况下,函数返回并评估值。所以,它会工作得很好。

这实际上是什么让表达式模板的工作:他们可以继续持有引用到那种临时工在表达类似

e = a + b * c/d 

因为每个临时将持续到表达

x = y 

是完全评估。标准中的12.2 Temporary objects中有相当简洁的描述。

+1

我从来没有完全想到要获得该标准的副本。我应该把它作为优先事项。 – 2009-02-25 06:11:53

+1

@JohannesSchaub:在这种情况下什么是“全表达式”:`printf(“%s”,strdup(std :: string(“$$$”)。c_str()))``我的意思是如果` strdup(std :: string(“$$$”)。c_str())被视为完整表达式,那么`strdup`指向的指针是* valid *。如果`std :: string(“$$$”)。c_str()`是一个完整表达式,那么`strdup`看到的指针就是* invalid *!您能否基于这个例子多解释一下? – 2014-12-09 13:00:04

4

对GetString的调用返回后。

2

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

下应该编译:

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

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

请注意,C++ 0x只改变了一点,因为它增加了右值引用和移动语义。本质上通过使用右值参考参数(用& &表示),我可以将右值“移动”到函数中(而不是复制它),并且右值的生存期可以绑定到它所移入的对象,而不是表达式。有一个非常好的blog post from the MSVC team on that walks through this in great detail,我鼓励人们阅读它。

移动右值的教学示例是临时字符串,我将在构造函数中显示赋值。如果我有一个类MyType的包含字符串成员变量,它可以用右值在构造函数初始化像这样:

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

这是很好的,因为当我宣布这个类的临时对象的实例:

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

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

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不起作用。但是如果当前的编译器仍然有问题,我会感到惊讶。