C++: возврат ссылки std:: string из памяти стека

я начну с того, что прочитал эту тему:C++ возврат ссылки / стека памяти. Но там вопрос был сstd::vector<int> как объект-тип. Но я хотя поведение std::string было по-другому. Не был ли этот класс специально создан для использования строк, не беспокоясь об утечках памяти и неправильном использовании памяти?

Итак, я уже знаю, что это неправильно:

std::vector<t> &function()
{
    vector<t> v;
    return v;
}

но это неправильно, так как ну?

std::string &function()
{
    string s = "Faz";
    s += "Far";
    s += "Boo";
    return s;
}

спасибо


дополнительный вопрос (редактирование): Итак, я прав, когда говорю возвращается (по стоимости) an std::string не копирует последовательность символов, только указатель на char * массив и t_size для длины?

если это утверждение верно, является ли это допустимым способом создания глубокой копии строки (чтобы избежать замены, изменит строку)?

string orig = "Baz";
string copy = string(orig);

5 ответов


вы очень близки к тому, чтобы эти функции работают:

std::string function()
{
    string s = "Faz";
    s += "Far";
    s += "Boo";
    return s;
}

просто сделайте так, чтобы они вернули копию вместо ссылки, и вы настроены. Это то, что вы хотите, копия строки на основе стека.

это тоже становится лучше, потому что оптимизация возвращаемого значения (RVO) создаст строку только один раз и вернет ее, так же, как если бы вы создали ее в куче и вернули ссылку на нее, все за кулисами!


не имеет значения, какой тип; этот шаблон всегда полностью, 100% неправильно для любого типа объекта T:

T& f() {
    T x;
    return x;
}   // x is destroyed here and the returned reference is thus unusable

Если вы возвращаете ссылку из функции, вы должны убедиться, что объект, на который она ссылается, все еще будет существовать после возвращения функции. Поскольку объекты с автоматическим сроком хранения уничтожаются в конце блока, в котором они объявлены, они гарантированы не существовать после завершения функции.


не возвращайте ссылки, возвращайте по значению:

std::string function() // no ref
{
    string s = "Faz";
    s += "Far";
    s += "Boo";
    return s;
}

Если ваш компилятор может сделать именованную оптимизацию возвращаемого значения, ака NRVO, (что вероятно), он преобразует это во что-то примерно эквивалентное следующему, что позволяет избежать любых посторонних копий:

// Turn the return value into an output parameter:
void function(std::string& s)
{
    s = "Faz";
    s += "Far";
    s += "Boo";
}

// ... and at the callsite,
// instead of:
std::string x = function();
// It does this something equivalent to this:
std::string x; // allocates x in the caller's stack frame
function(x); // passes x by reference

относительно дополнительного вопроса:

конструктор копирования string всегда делает глубокую копию. Таким образом, если есть задействованные копии, нет проблем с псевдонимами. Но при возвращении по значению с NRVO, как вы можете видеть выше, никаких копий.

вы можете сделать копии, используя несколько различных синтаксисов:

string orig = "Baz";
string copy1 = string(orig);
string copy2(orig);
string copy3 = orig;

второй и третий не имеют семантической разницы: они оба просто инициализация. Первый создает временное путем явного вызова конструктора копирования, а затем инициализирует переменную с копией. Но компилятор может сделать копию elision здесь (и очень вероятно, что это будет) и сделает только одну копию.


проблема с этим (независимо от типа) заключается в том, что вы возвращаете ссылку на память, которая выходит из области действия, когда возвращается.

std::string &function()
{
    string s = "Faz";
    s += "Far";
    s += "Boo";

    // s is about to go out scope here and therefore the caller cannot access it
    return s;
}

вы хотите изменить тип возврата, чтобы он не был ссылочным, а по значению, поэтому возвращается копия s.

std::string function()
{
    string s = "Faz";
    s += "Far";
    s += "Boo";

    // copy of s is returned to caller, which is good
    return s;
}

вы можете взять адрес возвращаемой строки и сравнить его с адресом исходной строки, как показано ниже:

#include <iostream>    
using namespace std;

string f() {
    string orig = "Baz";
    string copy1 = string(orig);
    string copy2(orig);
    string copy3 = orig;

    cout << "orig addr: " << &orig << endl;
    cout << "copy1 addr: " << &copy1 << endl;
    cout << "copy2 addr: " << &copy2 << endl;
    cout << "copy3 addr: " << &copy3 << endl;
    return orig;
}

int main() {
    string ret = f();
    cout << "ret addr: " << &ret << endl;
}

я получил следующее:

orig addr: 0x7ffccb085230
copy1 addr: 0x7ffccb0851a0
copy2 addr: 0x7ffccb0851c0
copy3 addr: 0x7ffccb0851e0
ret addr: 0x7ffccb085230

видишь orig и ret указывают на тот же экземпляр строки в памяти, так orig возвращается по ссылке. copy1, copy2, copy3 копии orig потому что они указывают на разные объекты в памяти.