const&
От: Videoman Россия https://hts.tv/
Дата: 10.10.25 08:25
Оценка:
Опять что-то перемудрили в стандарте или я неправильно понимаю ссылочные квалификаторы у методов класса?
Обнаружил падение у себя в коде. В сухом остатке вот выжимка. Код содержит undefined behavior.
class nested_class 
{
public:
   int get() const { return m_member; };

private:
    int m_member = 0;
};

class holder_class
{
public:

    template<typename type_t>
    const nested_class& get() const& { return m_member; }
    
private:

    nested_class m_member;
};


int main(int argc,char** argv) {

    const nested_class& nested = holder_class().get<int>(); // UB

    return nested.get();
}

В оправдании себя: думал что 'const&' квалификатор метода класса убережет меня от вызова метода у временного объекта. Но не уберегло, не фортануло. Стандарт позволяет вызвать метод у временного константного объекта. Дальше масса объяснений зачем это, что ссылка константная и она продлевается и это безопасно и бла-бла-бла. Во у меня рабочий пример и таки приведение оказалось не безопасным. Понятно, что можно добавить метод
template<typename type_t>
nested_class get() const&& = delete;
и всё будет работать как задумано. Но почему такое поведение по умолчанию, где я ошибся в своей логике-реализации ?
Отредактировано 10.10.2025 8:52 Videoman . Предыдущая версия .
Re: const&
От: watchmaker  
Дата: 10.10.25 09:32
Оценка: 136 (8) +1
Здравствуйте, Videoman, Вы писали:

V>Опять что-то перемудрили в стандарте или я неправильно понимаю ссылочные квалификаторы у методов класса?


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


V> В сухом остатке вот выжимка.


Всё ещё слишком много лишнего :)

Более минимизированный пример c падением во время работы: https://godbolt.org/z/e1EnbbhvK
int main() {
    const int& bad = std::min(5, 6);
    printf("%d\n", bad); // UB
}


V> Стандарт позволяет вызвать метод у временного константного объекта.


И не только у метода — это опять же слишком частный случай.
Всегда и везде можно передавать временный объект по константной ссылке. Иначе бы такой код просто бы не компилировался:
const int good = std::min(5, 6);

Это дизайн языка, что по константной ссылке можно передать что угодно: число, строку или *this.

V>Понятно, что можно добавить метод
V>template<typename type_t>
V>nested_class get() const&& = delete;
V>
и всё будет работать как задумано.


Не нужно, пожалуйста, добавлять такие запрещения: от них вреда больше, чем пользы. Во-первых, они не все случаи ловят, во-вторых, они запрещают множество совершенно легитимных способов использовать классы:
printf("%d\n", holder_class().get<int>().get());

В твоём случае этот код не скомпилируется, хотя никакого UB нет.


Гораздо более практичный способ избежать подобных ошибок — использовать lifetimebound-атрибут, поддержка которого уже давно есть во многих компиляторах:
    
const nested_class& get() const& [[lifetimebound]];

После чего получать человекочитаемые сообщения:
error: temporary bound to local reference 'nested' will be destroyed at the end of the full-expression [-Werror,-Wdangling]
      |     const nested_class& nested = holder_class().get<int>(); 
      |                                  ^~~~~~~~~~~~~~

и при этом не получать ложных срабатываний при валидном использовании, в отличии от трюка с && = delete.

Демо: https://godbolt.org/z/ea6ovve7v
Re: const&
От: · Великобритания  
Дата: 10.10.25 09:37
Оценка:
Здравствуйте, Videoman, Вы писали:

V>В оправдании себя: думал что 'const&' квалификатор метода класса убережет меня от вызова метода у временного объекта.

Если совсем на пальцах, то потому что const это совсем не то же самое, что immutable.
const совершенно не значит, что значение всегда будет хорошим, правильным и неизменяемым. Это лишь значит, что тебе менять не дадут.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: const&
От: watchmaker  
Дата: 10.10.25 10:31
Оценка:
W>Более минимизированный пример c падением во время работы: https://godbolt.org/z/e1EnbbhvK
W>int main() {
W>    const int& bad = std::min(5, 6);
W>    printf("%d\n", bad); // UB
W>}


Кстати, с современной STL от llvm или от microsoft в этом коде теперь проблема детектируется во время компиляции: https://godbolt.org/z/rYG74fhsz
Потому что они уже начали размечать код в STL как раз lifetimebound-атрибутами. Хотя, к сожалению, пока только начали и покрытие всё ещё не очень большое.
Re: const&
От: rg45 СССР  
Дата: 10.10.25 11:17
Оценка: 1 (1) +1
Здравствуйте, Videoman, Вы писали:

V>Опять что-то перемудрили в стандарте или я неправильно понимаю ссылочные квалификаторы у методов класса?


V>В оправдании себя: думал что 'const&' квалификатор метода класса убережет меня от вызова метода у временного объекта. Но не уберегло, не фортануло. Стандарт позволяет вызвать метод у временного константного объекта. Дальше масса объяснений зачем это, что ссылка константная и она продлевается и это безопасно и бла-бла-бла. Во у меня рабочий пример и таки приведение оказалось не безопасным.


V>Но почему такое поведение по умолчанию, где я ошибся в своей логике-реализации ?


Разницу между 'const' и 'const&' ты можешь обнаружить только при наличии в классе других перегрузок. Например, перегрузка по 'const' приводит к коллизии с перегрузкой по '&&', а перегрузка по 'const&' не приводит. В остальном же 'const' и 'const&' ведут себя одинаково. Т.е. обе эти перегрузки могут работать для rvalue-выражений. Т.е. правила в принципе те же самые, что и с явными параметрами обычных функций.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 10.10.2025 11:23 rg45 . Предыдущая версия .
Re[2]: const&
От: Videoman Россия https://hts.tv/
Дата: 10.10.25 11:47
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, Videoman, Вы писали:


V>>В оправдании себя: думал что 'const&' квалификатор метода класса убережет меня от вызова метода у временного объекта.

·>Если совсем на пальцах, то потому что const это совсем не то же самое, что immutable.
·>const совершенно не значит, что значение всегда будет хорошим, правильным и неизменяемым. Это лишь значит, что тебе менять не дадут.

Я прекрасно понимаю что такое 'const'. Вопрос был не в этом. Скорее удивление от того, что если реализовать 'const&' и 'const&&', то всё работает логично, а вот если только 'const&' то временные this спокойно делегируют все вызовы к последнему.
Re[2]: const&
От: Videoman Россия https://hts.tv/
Дата: 10.10.25 11:55
Оценка:
Здравствуйте, watchmaker, Вы писали:

Спасибо за исчерпывающий ответ.

W>Не нужно, пожалуйста, добавлять такие запрещения:


Я не пишу так код в продакшене. Удивление возникло в процессе отладки нового кода, в который ещё не была добавлена реализация перегрузки для 'const&&'.

W>Гораздо более практичный способ избежать подобных ошибок — использовать lifetimebound-атрибут


Спасибо, возьму на заметку. Но пока я вынужден работать со старыми компиляторами.
Отредактировано 10.10.2025 12:08 Videoman . Предыдущая версия .
Re: const&
От: Teolog  
Дата: 11.10.25 14:18
Оценка:
Это use after free.
Взята ссылка на внутренность временного обьекта, обьект умер по выходу из области видимости, ссылка разименована.
Что сложного завести локальную переменную?
Компилятор просто недостаточно умен чтобы бить вас по рукам.
Re[2]: const&
От: Videoman Россия https://hts.tv/
Дата: 12.10.25 14:10
Оценка:
Здравствуйте, Teolog, Вы писали:

T>Это use after free.

T>Взята ссылка на внутренность временного обьекта, обьект умер по выходу из области видимости, ссылка разименована.
T>Что сложного завести локальную переменную?
T>Компилятор просто недостаточно умен чтобы бить вас по рукам.

Вы сейчас на какой вопрос отвечаете?
Re[2]: const&
От: Кодт Россия  
Дата: 12.10.25 18:47
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Гораздо более практичный способ избежать подобных ошибок — использовать lifetimebound-атрибут, поддержка которого уже давно есть во многих компиляторах:


Вот это — богоугодно!
Жалко лишь, что это свойство нельзя просто так взять и протащить в систему типов... по аналогии с тем, как это есть в расте или клине.

Или можно? Хотя бы в виде какого-то костылесипеда...
template<class T> class wrapper {
  const T& src_;
public:
  wrapper(const T& src) : src_{src} {}
  operator const T&() const [[lifetime]] { return src_; }
};

class owner {
  some x_;
  another y_;
  .....
public:
  auto x() const&& [[lifetime]] { return wrapper{x_}; }
  auto y() const&& [[lifetime]] { return wrapper{y_}; }
};

(нерабочий эскиз, мысль иссякла)
Перекуём баги на фичи!
Re[2]: const&
От: rg45 СССР  
Дата: 12.10.25 23:08
Оценка: 2 (1)
Здравствуйте, watchmaker, Вы писали:

W>Демо: https://godbolt.org/z/ea6ovve7v


    template<typename type_t>
    const nested_class& get() const& LIFETIMEBOUND;


Я правильно понимаю, что здесь уже можно упростить 'const&' до более привычного 'const' без какого-либо ущерба?
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 12.10.2025 23:09 rg45 . Предыдущая версия .
Re[3]: const&
От: watchmaker  
Дата: 14.10.25 10:39
Оценка: 10 (2)
Не понял, что ты хотел сделать, но wrapper написан не так.
Нужно что-то вроде этого:
template<class T> class wrapper {
  const T& src_;
public:
  wrapper(const T& src LIFETIMEBOUND); // wrapper не должен использоваться позже разрушения src
  operator const T&(this const wrapper self LIFETIMEBOUND); // время жизни должно следить за оригинальным src_, a не за wrapper. Ведь возвращённой ссылкой легально пользоваться, если owner ещё жив, даже если wrapper уже разрушен
};


Как видно по второй строке, у атрибута слегка неочевидные отличия в поведении в зависимости от того, применяется ли он к const wrapper или к const wrapper& (аналогично различие при применении к wrapper и к wrapper*).

Сейчас атрибут покрывает 90% случаев самых частых проблем. Но им нельзя или сложно выразить правила владения во всяких сложных структурах с умными указателями и подобным как раз из-за недостаточной выразительности. Не всегда получается просто объяснить, что нужно следить за временем жизни объекта, на который ссылается указатель, а не за самим указателем, когда эти сами указатели завёрнуты в другие указатели или ссылки.

А дискуссия про развитие правил описания немного зависла: https://discourse.llvm.org/t/rfc-lifetime-annotations-for-c/61377/40

С другой стороны, даже сейчас возможность уменьшить на 90% число банальных багов вокруг разных string_view — это уже неплохо.
Кучу кода видел, который проезжался по памяти, но выглядел при этом обманчиво нормально:
const std::string& get() {
  static std::string foo = "foo";
  return foo;
}


std::string_view value = cond ? get() : "default"; // "default" живёт вечно, строка с "foo" - тоже до окончания программы. 
std::cout << value << '\n'; // значит string_view ссылается на живые данные?

а теперь компилятор находит в нём ошибки во время компиляции.
Re[3]: const&
От: watchmaker  
Дата: 14.10.25 10:47
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

V> пока я вынужден работать со старыми компиляторами.


Замечу, что не нужно ждать поддержки во всех компиляторах. Достаточно одного. Или даже в статическом анализаторе (вроде clang-tidy), подключённом к проекту.
Ведь атрибуты в С++ обратно совместимы, и код под старые компиляторы продолжит собираться, даже если компилятор не понимает что с ними делать. А для репорта ошибок хватает обычно и одного источника.
Re[3]: const&
От: watchmaker  
Дата: 14.10.25 10:52
Оценка: +1
Здравствуйте, rg45, Вы писали:


R>Я правильно понимаю, что здесь уже можно упростить 'const&' до более привычного 'const' без какого-либо ущерба?


Если нет каких-либо других перегрузок метода, то да, конечно.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.