Идея поста родилась при употреблении чая во внутрь на южной кухне.
Недавно смотрел один доклад (точнее бегло просматривал) про Rust и в момент, когда начался рассказ про life-time, глаз зацепился за такой опасный пример из мира C++:
string get_url()
{
return "http://htrd.su";
}
string_view get_schema(string_view v)
{
// тут какие-то действия, я их опущу
auto result = v;
return result;
}
int main()
{
auto v = get_schema(get_url());
}
Что такое string_view - смотреть тут или тут. Если коротко - это невладеющая строка. Полезна для экономии на аллокациях, когда нужно работать с частями исходной строки.
В общем, из природы string_view
следует и проблемы в коде выше: get_url()
вернёт временный объект, который будет уничтожен в конце выражения, а следовательно, v
будет ссылаться на невалидный участок памяти.
У меня в голове родилось, сходу, вариант защиты от такого: так как string_view
не владеет строкой, то перемещение для строки сделать невозможно (да и семантически неверно), а перемещающий конструктор будет предпочтён для временного объекта. Следовательно если сделать перемещающий конструктор для string
у string_view
удалённым, то код выше сломается на этапе компиляции.
Подумав про это, решил глянуть, неужели разработчики стандарта об этом не подумали тоже… Оказалось нет:
- http://en.cppreference.com/w/cpp/string/basic_string_view/basic_string_view - запрещения данного конструктора нет
- http://www.boost.org/doc/libs/1_60_0/libs/utility/doc/html/string_ref.html#string_ref.reference - аналогичная ситуация
Проверил концепт на простом примере:
struct string_view
{
string_view() = default;
string_view(const string_view& other) = default;
string_view(const string& str)
: ptr(str.c_str()), size(str.length())
{}
string_view(const char* str)
: ptr(str), size(::strlen(str))
{}
string_view(const char* str, size_t len)
: ptr(str), size(len)
{}
// Protection for temporary objects
string_view(string&& str) = delete;
string_view& operator=(const string_view&) = default;
const char *ptr = nullptr;
size_t size = 0;
};
Как видим, я запретил перемещение для std::string
. И теперь код из примера выше не собирается с такой ошибкой:
prog.cpp: In function 'int main()':
prog.cpp:56:33: error: use of deleted function 'string_view::string_view(std::string&&)'
auto v = get_schema(get_url());
^
prog.cpp:23:2: note: declared here
string_view(string&& str) = delete;
^
Ошибочным является и тоже не соберётся такой код:
string_view v2;
v2 = get_url();
При этом прочие валидные сценарии продолжают работать:
// 1
string str = get_url();
auto v1 = get_schema(str);
// 2
string_view v2 = "hello";
// 3
auto v3 = get_schema("http://htrd.su");
Нельзя не сказать, что ищущий всегда найдёт способ обойти защиты и отстрелить ногу из гаубицы:
string_view get_schema2(string url)
{
string_view v(url);
return v;
}
const char* get_schema3(string url)
{
return url.c_str();
}
...
// WA for protection
auto v1 = get_schema2(get_url());
auto v2 = get_schema(get_url().c_str());
string_view v3 = get_schema3(get_url());
Такой код соберётся, но будет повторять ошибку из самого первого примера: использование уже освобождённой памяти. В общем: ищущий да обрящет.
Собственно у меня резонный вопрос: почему такая защита не сделана в Boost и экспериментальных реализация string_view в STL? Я, быть может, не вижу какой-то очевидной (или не очень) вещи, которая препятствует этому?
PS ну и полный листинг и ссылка на ideone:
#include <iostream>
#include <cstring>
using namespace std;
struct string_view
{
string_view() = default;
string_view(const string_view& other) = default;
string_view(const string& str)
: ptr(str.c_str()), size(str.length())
{}
string_view(const char* str)
: ptr(str), size(::strlen(str))
{}
string_view(const char* str, size_t len)
: ptr(str), size(len)
{}
// Protection for temporary objects
string_view(string&& str) = delete;
string_view& operator=(const string_view&) = default;
const char *ptr = nullptr;
size_t size = 0;
};
string get_url()
{
return "http://htrd.su";
}
string_view get_schema(string_view uri)
{
return uri;
}
string_view get_schema2(string url)
{
string_view v(url);
return v;
}
const char* get_schema3(string url)
{
return url.c_str();
}
int main()
{
{
// Compilation error:
//auto v1 = get_schema(get_url());
//string_view v2;
//v2 = get_url();
}
{
// WA for protection
auto v1 = get_schema2(get_url());
auto v2 = get_schema(get_url().c_str());
}
{
// Valid usage
string str = get_url();
auto v1 = get_schema(str);
string_view v2 = "hello";
auto v3 = get_schema("http://htrd.su");
}
return 0;
}