#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. Большое вам спасибо 🙂