Пуленепробиваемый временный срок службы C ++? - PullRequest
2 голосов
/ 30 сентября 2019

Возвращаясь к расширению времени жизни в C ++, я обнаружил, что есть некоторые шаблоны, которые нарушают «разложимость» выражений C ++. Например, следующие два блока представляют собой допустимый код C ++ :

class NonMovable {
public:
   NonMovable(NonMovable&&) = delete;
   NonMovable(const NonMovable&) = delete;
   NonMovable();

   int Value() const;
};

template <class T>
const T& identity(const T& x) {
    return x;
}

template <class T>
class A {
public:
    explicit A(const T& value) : value_(value) {}
    const T& GetValue() const {
        return value_;
    }
private:
    const T& value_;
};

Правильное использование:

int main() {
    int retcode = identity(
        identity(/*tmp1*/ A(/*tmp2*/ NonMovable{}).GetValue())).Value();
    // tmp1 and tmp2 end their lifetimes here: 
    // their full-expression is the whole previous line
    return retcode;
}

Но если мы разложим первое выражение в main, он становится недействительным:

int main() {
    auto&& a_obj = /*tmp1*/ A(/*tmp2*/ NonMovable{});
    // tmp2 lifetime ends here

    // oops! dereferencing dangling reference:
    int retcode = identity(
        identity(a_obj.GetValue())).Value();
    return retcode;
    // tmp1 lifetime ends here
}

Мой вопрос: Возможно ли отключить второй вид использования?

PS: я не совсем уверенесли второй main вводит UB, потому что я протестировал с clang -Wlifetime, и он не жалуется. Но я все еще верю, что это UB. В реальной жизни я сталкивался с подобным поведением: код ломался, выдавая предупреждения UBSan и ошибки по умолчанию, если я разложил одно выражение на два отдельных.

PPS: эти identity s на самом деле не имеют значениямного, если я правильно понимаю время жизни объекта (в чем я сейчас сомневаюсь)

Ответы [ 2 ]

3 голосов
/ 30 сентября 2019

Ваш анализ верен. Без продления времени жизни все временные объекты уничтожаются в конце «полного выражения», то есть ; в конце строки. Поэтому, когда вы говорите

int retcode = A(NonMovable{}).GetValue().Value();

(комментарии и identity звонки удалены для ясности), тогда все в порядке;NonMovable объект еще жив, когда вы запрашиваете его значение.

С другой стороны, когда вы говорите

auto&& a_obj = A(NonMovable{});

, тогда NonMovable уничтожается в концелинии, и объект A будет содержать висячую ссылку. (Кроме того, auto&& просто на всю жизнь продлевает временный A здесь - вы также можете просто использовать обычный auto)

Мой вопрос: возможно ли отключитьВторой вид использования?

Не совсем, по крайней мере, насколько я знаю. Вы можете добавить удаленный конструктор A(NonMovable&&), но это также помешает «правильному» использованию, как в первом примере. Это точно та же проблема, которая возникает с std::string_view (и будет происходить с std::span в C ++ 20) - по существу, ваш A класс имеет ссылочную семантику, но ссылается на временный объект, который был уничтожен.

1 голос
/ 30 сентября 2019

Таким образом, используя коллективный разум, в комментариях к вопросу нам удалось придумать следующую реализацию A, которая может быть применима к некоторым случаям использования (но не к использованию std::span или std::string_view). ):

struct Dummy;

template <class T>
class A {
public:
    explicit A(const T& value) : value_(value) {}

    template <class TDummy = Dummy>
    const T& GetValue() const& {
        static_assert(!std::is_same_v<TDummy, Dummy>, 
        "Stop and think, you're doing something wrong!" 
        "And in any case, don't use std::move on this class!");
    }

    const T& GetValue() && {
        return value_;
    }
private:
    const T& value_;
};

Теперь, если попытаться скомпилировать следующий код, он получит описательное сообщение об ошибке:

int main() {
    auto&& a_obj = A(NonMovable{});
    // will not compile:
    int retcode = identity(
        identity(a_obj.GetValue())).Value();
    return retcode;
}

Причина в том, что decltype((a_obj)) == A<NonMovable>&, поэтому он связываетк методу, который приводит к ошибке времени компиляции.

Он удовлетворяет моим сценариям использования, но, к сожалению, это не универсальное решение - оно зависит от того, что нужно от class A.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...