Что происходит, когда строковый литерал передается функции, принимающей const std::string

#c #stdstring

#c #stdstring

Вопрос:

как вы, вероятно, можете догадаться из названия, я хочу понять, что именно происходит, когда std::string передается функции в качестве ссылки const, потому что ранее сегодня я столкнулся с несколькими ситуациями, которые я не совсем понял. Вот некоторый код:

 #include <string>
#include <stdio.h>

struct Interface {
    virtual void String1(const std::string amp;s) = 0;
    virtual void String2(const std::string amp;s) = 0;
    virtual void DoSomething() = 0;
};

struct SomeClass : public Interface {
    void String1(const std::string amp;s) override { s1 = s.c_str(); }
    void String2(const std::string amp;s) override { s2 = s.c_str(); }
    void DoSomething() override { printf("%s - %sn", s1, s2); }

private:
    const char *s1, *s2;
};

struct AnotherClass {
    AnotherClass(Interface *interface) : interface(interface) {
        this->interface->String1("Mean string literal");
    }

    void DoTheThing() {
        std::string s("Friendlich string literal");
        interface->String2(s);
        interface->DoSomething();
    }

private:
    Interface *interface = nullptr;
};

int main(int argc, char **argv) {
    SomeClass some_class;
    AnotherClass another_class(amp;some_class);

    another_class.DoTheThing();
}
  

При использовании const char * для s1 и s2 в SomeClass программа печатает строковый литерал Friendlich — строковый литерал Friendlich или [какой-то мусор] — строковый литерал Friendlich вместо обычного строкового литерала — строкового литерала Friendlich, как я ожидал.

При переключении на std::string для s1 и s2 он работает как ожидалось, выводя строковый литерал Mean — Friendlich строковый литерал.

Мы с коллегой предполагаем, что строка в ctor другого класса выходит за пределы области видимости, но SomeClass по-прежнему хранит адрес строки из-за c_str().

При использовании std::string вместо const char * для s1 и s2 фактически создается копия, поэтому выход за пределы области видимости не является проблемой. Вот так:

 struct SomeClass : public Interface {
    void String1(const std::string amp;s) override { s1 = s; }
    void String2(const std::string amp;s) override { s2 = s; }
    void DoSomething() override { printf("%s - %sn", s1.c_str(), s2.c_str()); }

private:
    std::string s1, s2;
};
  

Итак … что происходит на самом деле? Почему это не работает с const char *? Почему это работает с std::string?

Ответ №1:

Когда вы передаете строковый литерал в функцию, которая принимает const std::stringamp; , происходят следующие события:

  • Строковый литерал преобразуется в const char*
  • Создается временный std::string объект. Выделяется ее внутренний буфер, который инициализируется копированием данных из const char* до тех пор, пока не будет видно завершающее значение null. Параметр ссылается на этот временный объект.
  • Выполняется тело функции.
  • Предполагая, что функция возвращается нормально, временный объект уничтожается в какой-то неопределенный момент между возвратом функции и окончанием вызывающего выражения.

Если c_str() указатель сохраняется из параметра, он становится висячим указателем после уничтожения временного объекта, поскольку он указывает на внутренний буфер временного объекта.

Аналогичная проблема возникнет, если функция принимает std::string . std::string Объект будет создан при вызове функции и уничтожен при возврате функции или вскоре после этого, поэтому любой сохраненный c_str() указатель станет зависшим.

Однако, если функция принимает const std::stringamp; и аргумент имеет тип std::string , при вызове функции новый объект не создается. Ссылка ссылается на существующий объект. c_str() Указатель будет оставаться действительным до тех пор, пока исходный std::string объект не будет уничтожен.

Комментарии:

1. И что происходит с const char *, который создается из строкового литерала?

2. @Marco Сам указатель также является временным объектом, который уничтожается в конце полного выражения. Это не имеет значения, если вы не собираетесь сохранять указатель или ссылку на сам указатель. Это похоже на то, как мы обычно не беспокоимся о времени жизни 3 в выражении 2 3 .

3. Итак … если я изменю параметр на const char *, он сохранит его до тех пор, пока он больше не понадобится SomeClass?

4. @Marco Если вы измените параметр на const char* , то это значение указателя будет указывать на строковый литерал. Строковый литерал будет действовать до завершения программы. Следовательно, безопасно создать копию этого указателя и разыменовать его позже. Однако сохранение указателя на сам параметр указателя было бы небезопасно, поскольку параметр указателя был бы уничтожен после возврата функции.

Ответ №2:

char * Это не объект, это указатель на символы, которые существуют в каком-то другом контексте. Если вы назначаете такой указатель на временную переменную или данные, содержащиеся во временной переменной, он будет недействительным, когда временная переменная будет уничтожена. Его использование после этой точки приводит к неопределенному поведению.

Когда у вас есть переменные-члены std::string , копия создается во время присвоения, поэтому не имеет значения, уничтожено временное значение или нет.

Комментарии:

1. Но почему он копирует? Потому что тип переменной-члена — std::string? Но разве аргумент не является ссылкой?

2. @Marco a std::string всегда содержит свои собственные символьные данные, поэтому, если он принимает ссылку, он все равно создает свою собственную копию.

3. Большое вам спасибо 🙂