Đảm bảo thời gian tồn tại tạm thời trong C ++?


103

C ++ có cung cấp sự đảm bảo cho thời gian tồn tại của một biến tạm thời được tạo trong một lệnh gọi hàm nhưng không được sử dụng làm tham số không? Đây là một lớp mẫu:

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ì hàm hủy cho đối tượng StringBuffer tạm thời sẽ được gọi? Là nó:

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

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

Cảm ơn.


tại sao không kế thừa và nạp chồng hoặc tạo một hàm toàn cục? tôi sẽ sạch sẽ hơn và Bạn sẽ không phải tạo một lớp ony để gọi một thành viên.
Jacek Ławrynowicz

1
Nếu bạn định sử dụng điều này, bạn nên gọi m_str.reserve(maxlength)vào char * Size(int maxlength), nếu không trình hủy có thể ném.
Mankarse

Câu trả lời:


109

Hàm hủy cho loại thời gian tạm thời đó đượ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ẽ hoạt động tốt.

Trên thực tế, đó là điều làm cho các mẫu biểu thức hoạt động: Chúng có thể giữ các tham chiếu đến loại thời gian tạm thời đó 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 cho đến khi biểu thức

x = y

Được đánh giá hoàn toàn. Nó được mô tả khá ngắn gọn 12.2 Temporary objectstrong Tiêu chuẩn.


3
Tôi chưa bao giờ tìm được một bản sao của tiêu chuẩn. Tôi nên ưu tiên nó.
Mark Ransom

2
@JohannesSchaub: "Biểu thức đầy đủ" 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 coi là biểu thức đầy đủ, thì con trỏ strdupnhìn thấy là hợp lệ . Nếu std::string("$$$").c_str()là một biểu thức đầy đủ, thì con trỏ strdupnhìn thấy không hợp lệ ! Bạn có thể vui lòng giải thích thêm một chút dựa trên ví dụ này?
Grim Fandango

2
@GrimFandango AIUI toàn bộ của bạn printflà biểu hiện đầy đủ. Do đó, đây strduplà một rò rỉ bộ nhớ không cần thiết - bạn có thể để nó in c_str()trực tiếp.
Josh Stone

1
"Đó là loại tạm thời" - Đó là loại gì? Làm cách nào để biết liệu tạm thời của tôi có phải là "loại tạm thời" không?
RM

@RM đủ công bằng. Ý tôi là "những cái mà bạn tạo trong một đối số hàm", như được thực hiện trong câu hỏi.
Johannes Schaub - litb

18

câu trả lời của litb là chính xác. Thời gian tồn tại 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à trình hủy cho đối tượng tạm thời được gọi ở cuối biểu thức đầy đủ và khi trình hủy trên StringBuffer được gọi, trình hủy trên m_buffer cũng sẽ được được gọi, nhưng không phải hàm hủy trên m_str vì nó là một tham chiếu.

Lưu ý rằng C ++ 0x chỉ thay đổi mọi thứ một chút vì nó thêm các tham chiếu rvalue và di chuyển ngữ nghĩa. Về cơ bản bằng cách sử dụng một tham số tham chiếu rvalue (ký hiệu là &&), tôi có thể 'di chuyển' giá trị vào hàm (thay vì sao chép nó) và thời gian tồn tại của rvalue có thể bị ràng buộc với đối tượng mà nó di chuyển vào chứ không phải biểu thức. Có một bài đăng trên blog thực sự tốt từ nhóm MSVC trình bày rất chi tiết về vấn đề này 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ị phép gán trong một hàm tạo. Nếu tôi có một lớp MyType chứa một biến thành viên chuỗi, nó có thể được khởi tạo với một giá trị rvalue trong hàm tạo như sau:

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

Điều này rất hay vì khi tôi khai báo 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 ta tránh sao chép và phá hủy đối tượng tạm thời và "hello" được đặt trực tiếp bên trong biến thành viên của cá thể lớp sở hữu. Nếu đối tượng có trọng lượng nặng hơn một 'chuỗi' thì lệnh sao chép bổ sung và lệnh hủy có thể rất quan trọng.


1
Để thực hiện việc di chuyển Tôi nghĩ rằng bạn cần phải thả các const và sử dụng std :: di chuyển như MyType (std :: string && tên): m_name (std :: di chuyển (tên)) {}
gast128


3

StringBuffer nằm trong phạm vi của GetString. Nó sẽ bị phá hủy ở cuối phạm vi của GetString (tức là khi nó quay trở lại). 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 nên biên dịch:

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

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

Tôi đã chỉnh sửa câu hỏi. Tuy nhiên, 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.
Mark Ransom

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 (); Đối tượng & GetObj () {return & Object (); } // bad - sẽ để lại tham chiếu lơ lửng.
BigSandwich

1
Rõ ràng là tôi đang giải thích bản thân rất tệ và có thể tôi cũng không hiểu 100%. Hãy xem Informit.com/guides/content.aspx?g=cplusplus&seqNum=198 - nó cũng giải thích và trả lời câu hỏi ban đầu của tôi.
Mark Ransom

1
Cảm ơn, vì liên kết, điều đó có ý nghĩa ngay bây giờ.
BigSandwich

3

Tôi đã viết gần như chính xác cùng một 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 có tiêu chuẩn, mỗi trình biên dịch đã làm theo cách khác nhau. Tôi tin rằng Sách hướng dẫn tham khảo có chú thích cũ cho C ++ đã chỉ định rằng các khoảng thời gian tạm thời sẽ được dọn dẹp ở 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ẽ rất ngạc nhiên nếu bất kỳ trình biên dịch nào hiện tại vẫn bị hỏng.


Thật ma quái làm sao chúng giống nhau! Cảm ơn vì đã cảnh báo - nơi đầu tiên tôi sẽ thử là VC ++ 6, không được biết đến về sự tuân thủ các tiêu chuẩn của nó. Tôi sẽ xem xét cẩn thận.
Mark Ransom

Tôi đã viết lớp ban đầu trên VC ++ 6 nên nó không có vấn đề gì.
Daniel Earwicker
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.