2009-02-25 31 views
85

C++ có cung cấp sự đảm bảo về tuổi thọ của một biến tạm thời được tạo trong một cuộc gọi hàm nhưng không được sử dụng làm tham số? Dưới đây là một lớp dụ:Đảm bảo tuổi thọ tạm thời trong 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; 
}; 

Và đây là cách bạn sẽ sử dụng nó:

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

Khi nào thì destructor cho đối tượng StringBuffer tạm thời được gọi là? Có phải là:

  • Trước khi gọi đến GetString?
  • Sau khi trở về GetString?
  • Trình biên dịch phụ thuộc?

Tôi biết rằng C++ đảm bảo rằng biến tạm thời cục bộ sẽ hợp lệ miễn là có tham chiếu đến nó - điều này có áp dụng cho đối tượng cha khi có tham chiếu đến biến thành viên không?

Cảm ơn.

+0

tại sao không kế thừa và quá tải hoặc tạo chức năng toàn cầu? tôi sẽ sạch hơn và bạn sẽ không phải tạo một lớp học để gọi một thành viên. –

+1

Nếu bạn định sử dụng nó, bạn nên gọi 'm_str.reserve (maxlength)' trong 'char * Size (int maxlength)', nếu không thì destructor có thể ném. – Mankarse

Trả lời

93

Trình phá hủy cho loại thời gian đó được gọi ở cuối biểu thức đầy đủ. Đó là biểu hiện bên ngoài nhất mà không phải là một phần của bất kỳ biểu thức nào khác. Đó là trong trường hợp của bạn sau khi hàm trả về và giá trị được đánh giá. Vì vậy, nó sẽ làm việc tất cả tốt đẹp.

Đó là thực tế những gì làm cho biểu mẫu công việc: Họ có thể giữ tài liệu tham khảo giữ để mà loại temporaries trong một biểu thức như

e = a + b * c/d 

Bởi vì mỗi tạm thời sẽ kéo dài đến sự biểu hiện

x = y 

Is được đánh giá hoàn toàn. Nó được mô tả khá chính xác trong tiêu chuẩn 12.2 Temporary objects.

+1

Tôi chưa bao giờ nhận được một bản sao của tiêu chuẩn. Tôi nên ưu tiên. –

+1

@JohannesSchaub: "Toàn bộ biểu thức" là gì trong trường hợp này: 'printf ("% s ", strdup (std :: string (" $$$ "). C_str()));'? Ý tôi là nếu ' strdup (std :: string ("$$$"). c_str()) 'được lấy làm biểu thức đầy đủ, sau đó con trỏ mà' strdup' thấy là * hợp lệ *. Nếu 'std :: string (" $$$ ") c_str()' là một biểu thức đầy đủ, thì con trỏ mà 'strdup' thấy là * không hợp lệ *! Bạn có thể giải thích thêm một chút dựa trên ví dụ này không? –

+1

@GrimFandango AIUI toàn bộ 'printf' của bạn là biểu thức đầy đủ. Vì vậy, 'strdup' là một rò rỉ bộ nhớ không cần thiết - bạn chỉ có thể để nó in trực tiếp' c_str() '. –

4

Sau khi cuộc gọi đến GetString trả về.

2

StringBuffer nằm trong phạm vi GetString. Nó sẽ bị phá hủy vào cuối phạm vi của GetString (tức là khi nó trả về). Ngoài ra, tôi không tin rằng C++ sẽ đảm bảo rằng một biến sẽ tồn tại miễn là có tham chiếu.

Sau đây ta phải biên dịch: Câu trả lời

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

Tôi nghĩ rằng tôi đã phóng đại bảo lãnh - chỉ dành cho thời gian địa phương. Nhưng nó tồn tại. –

+0

Tôi đã chỉnh sửa câu hỏi. Dựa trên các câu trả lời được cung cấp cho đến nay, nó có vẻ là một điểm tranh luận tuy nhiên. –

+0

Tôi vẫn không nghĩ rằng chỉnh sửa của bạn là chính xác: Object & obj = GetObj(); Object & GetObj() {return & Object(); } // xấu - sẽ để lại tham chiếu lơ lửng. – BigSandwich

16

litb là chính xác. Tuổi thọ của đối tượng tạm thời (còn được gọi là rvalue) được gắn với biểu thức và hàm hủy đối với đối tượng tạm thời được gọi ở cuối biểu thức đầy đủ và khi hàm hủy trên StringBuffer được gọi, hàm hủy trên m_buffer cũng sẽ được được gọi, nhưng không phải là destructor trên m_str vì nó là một tham chiếu.

Lưu ý rằng C++ 0x thay đổi mọi thứ chỉ một chút vì nó thêm tham chiếu rvalue và di chuyển ngữ nghĩa. Về cơ bản bằng cách sử dụng tham số tham số rvalue (được ký hiệu với & &) Tôi có thể 'chuyển' giá trị vào hàm (thay vì sao chép nó) và tuổi thọ của rvalue có thể bị ràng buộc với đối tượng mà nó di chuyển vào, không phải biểu thức. Có một thực sự tốt blog post from the MSVC team on that walks through this in great detail và tôi khuyến khích mọi người đọc nó.

Ví dụ sư phạm cho việc di chuyển rvalue là các chuỗi tạm thời và tôi sẽ hiển thị bài tập trong một hàm tạo. Nếu tôi có một MyType lớp có chứa một biến thành viên chuỗi, nó có thể được khởi tạo với một rvalue trong constructor như vậy:

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

này là tốt đẹp bởi vì khi tôi tuyên bố một thể hiện của lớp này với một đối tượng tạm thời:

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

điều xảy ra là chúng tôi tránh sao chép và hủy đối tượng tạm thời và "hello" được đặt trực tiếp vào biến thành viên của cá thể lớp riêng. Nếu đối tượng nặng hơn một 'chuỗi' thì bản sao thêm và cuộc gọi hủy có thể là đáng kể.

3

tôi đã viết gần như chính xác cùng lớp:

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

Trước khi tiêu chuẩn mỗi trình biên dịch đã làm nó khác đi. Tôi tin rằng Cẩm nang tham chiếu được chú thích cũ cho C++ đã chỉ ra rằng các thời gian nên dọn sạch ở cuối phạm vi, vì vậy một số trình biên dịch đã làm điều đó. Cuối năm 2003, tôi thấy rằng hành vi vẫn tồn tại theo mặc định trên trình biên dịch Forte C++ của Sun, vì vậy StringBuffer không hoạt động. Nhưng tôi sẽ ngạc nhiên nếu bất kỳ trình biên dịch hiện tại nào vẫn bị hỏng.

+0

Spooky chúng tương tự như thế nào! Cảm ơn bạn đã cảnh báo - nơi đầu tiên tôi sẽ thử là VC++ 6, không được biết đến với các tiêu chuẩn tuân thủ của nó. Tôi sẽ theo dõi cẩn thận. –

+0

Tôi đã viết lớp ban đầu trên VC++ 6 vì vậy nó không phải là một vấn đề. –

Các vấn đề liên quan