С ++ std :: порядок уничтожения кортежей

Есть ли правило, в котором говорится, в каком порядке уничтожаются члены std :: tuple?

Например, если Function1 возвращает std::tuple<std::unique_ptr<ClassA>, std::unique_ptr<ClassB>> в Function2, то могу ли я быть уверен, что (когда область действия Function2 останется) экземпляр ClassB, на который ссылается второй член, будет уничтожен до того, как экземпляр ClassA, на который ссылается первый член?

std::tuple< std::unique_ptr< ClassA >, std::unique_ptr< ClassB > > Function1()
{
    std::tuple< std::unique_ptr< ClassA >, std::unique_ptr< ClassB > > garbage;
    get<0>(garbage).reset( /* ... */ );
    get<1>(garbage).reset( /* ... */ );
    return garbage;
}

void Function2()
{
    auto to_be_destroyed = Function1();
    // ... do something else

    // to_be_destroyed leaves scope
    // Is the instance of ClassB destroyed before the instance of ClassA?
}

person z32a7ul    schedule 21.08.2016    source источник
comment
Я предполагаю, что это в основном зависит от того, как std::tuple реализован в вашей стандартной библиотеке.   -  person Arunmu    schedule 21.08.2016
comment
Я не могу найти в спецификации ничего, что определяло бы порядок уничтожения std::tuple. Вероятно, следует подать как неуказанное.   -  person 101010    schedule 21.08.2016
comment
stackoverflow.com/a/27663655/576911   -  person Howard Hinnant    schedule 22.08.2016
comment
Совсем недавно эта проблема возникла в докладе о достижении частичного отражения в excellet CPPCon 2016 Антония Полухина. в C ++ 14 без надлежащей языковой поддержки. IIRC он говорит, что единственная страшная вещь, которую ему пришлось сделать, - это переопределение std :: tuple для обеспечения определенного порядка инициализации. Также, пинг @ 101010 по этому поводу.   -  person einpoklum    schedule 16.10.2016


Ответы (3)


В стандарте не указан порядок уничтожения std::tuple. Тот факт, что §20.4.1 / p1 определяет, что:

Создание экземпляра кортежа с двумя аргументами аналогично созданию экземпляра пары с теми же двумя аргументами.

Подобный здесь не интерпретируется как идентичный и, следовательно, не подразумевается, что std::tuple должен иметь обратный порядок уничтожения своих аргументов.

Учитывая рекурсивный характер std::tuple, наиболее вероятно, что порядок уничтожения соответствует порядку его аргументов.

Я также основываю свои предположения на отчете об ошибке для GCC BUG 66699, где в обсуждении мои предположения выше оправданы.

При этом порядок уничтожения для std::tuple не указан.

person 101010    schedule 21.08.2016

В ответ на ваш вопрос я предложу жизненный урок, который я усвоил, а не прямой ответ:

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

В контексте кортежей - пожалуйста, будьте добры к людям, обслуживающим ваш код, и не позволяйте порядку уничтожения элементов кортежа потенциально испортить уничтожение других элементов. Это просто зло ... представьте себе незадачливого программиста, которому нужно будет отладить эту штуку. Фактически, эта бедняжка может стать вами через несколько лет, когда вы уже забыли о своем умном трюке из прошлого.

Если вам абсолютно необходимо полагаться на порядок уничтожения, возможно, вам следует просто использовать соответствующий класс с элементами кортежа в качестве членов данных (для которого вы могли бы написать деструктор, чтобы было ясно, что должно произойти в каком порядке) или какой-либо другой расположение, способствующее более четкому контролю разрушения.

person einpoklum    schedule 21.08.2016
comment
Это действительно важный урок! При программировании на C ++ этот совет всегда должен быть в умах разработчиков. - person hbobenicio; 22.08.2016
comment
Я не совсем понимаю это. Начиная с C ++ 11 строки должны иметь непрерывное хранилище. Но есть разумный аргумент (тот, который использовался при написании C ++ 03) не требовать этого. Или, если вы оспариваете этот пример, выберите другой, в котором стандарт выносит суждение, которое может быть обоснованным. Итак, не следует ли мне писать код, основанный на вещах, которые улучшены в C ++ 11 или C ++ 14? Или вы имеете в виду, что если стандарт не ясен, то не полагайтесь на то, что следующий программист будет трепать волосы так же, как вы? - person Steve Jessop; 22.08.2016
comment
Или для намеренно глупого примера, есть разумный аргумент, что std::vector следует называть std::dynamic_array, а std::valarray следует называть std::vector. Но я думаю, что очень разумно писать код на основе того, что std::vector - это то, о чем стандарт говорит: мы не можем использовать его, не предполагая этого. Так что я не думаю, что понял, к каким ситуациям применим ваш совет ;-) - person Steve Jessop; 22.08.2016
comment
@SteveJessop: О примере строк - обратите внимание, что весь код, который не предполагает, что строки имеют непрерывное хранилище, также работает в C ++ 03 (хорошо, если, конечно, он не полагается на функции C ++ 11). А если вы хотите полагаться на непрерывное хранилище, то делайте это, потому что для этого есть веская причина. Подумайте о том, насколько мало код <algorithm> предполагает и насколько широко он применим. Тем не менее, я допускаю, что вы могли бы преподнести мой жизненный урок префиксом При прочих равных условиях .... - person einpoklum; 02.09.2016
comment
@SteveJessop: Что касается вашего глупого примера, очевидно, что вы не можете не делать предположений относительно API библиотеки, которую используете. Но если бы вы увидели, что ваш коллега написал файл с именем vector.hpp, я бы не спешил предполагать, какой именно класс у него там есть. - person einpoklum; 02.09.2016
comment
В качестве дополнения: если вы действительно полагаетесь на подобный хитрый трюк, пожалуйста, пожалуйста задокументируйте точно, что вы делаете, с комментариями. Опишите поведение, которое вы эксплуатируете, и почему вы его эксплуатируете как можно подробнее, чтобы каждый, кто должен его поддерживать, знал, с чем имеет дело. - person Justin Time - Reinstate Monica; 16.10.2016

С Clang 3.4 я получаю одинаковый порядок уничтожения как для std::pair, так и для 2-го элемента std::tuple, а с g ++ 5.3 я получаю противоположный порядок, который может быть в основном из-за рекурсивной реализации std::tuple в libstd ++.

Итак, это в основном сводится к тому, что я сказал в комментарии, это определяется реализацией.

Из отчета ОШИБКА:

Комментарий от Martin Sebor

Поскольку макет членов std :: pair полностью определен, так же как и порядок их инициализации и уничтожения. Вывод тестового примера отражает этот порядок.

Порядок инициализации (и уничтожения) подобъектов std: stuple указан менее четко. По крайней мере, из того, что я прочитал спецификацию, не сразу очевидно, требуется ли какой-либо конкретный порядок.

Причина, по которой вывод std :: tuple с libstdc ++ является обратным для std :: pair, заключается в том, что реализация, основанная на рекурсивном наследовании, хранит и создает элементы кортежа в обратном порядке: т. Е. Базовый класс, который хранит последний элемент сохраняется и создается первым, за ним следует каждый производный класс (каждый из которых хранит последний - N-й элемент).

Цитата из стандарта [раздел 20.4.1], на который указывает репортер.

1 В этом подпункте описывается библиотека кортежей, которая предоставляет тип кортежа в качестве кортежа шаблона класса, который может быть создан с любым количеством аргументов. Каждый аргумент шаблона определяет тип элемента в кортеже. Следовательно, кортежи представляют собой разнородные коллекции значений фиксированного размера. Создание кортежа с двумя аргументами аналогично созданию экземпляра пары с теми же двумя аргументами. См. 20.3.

Аргумент против этого в связанной ошибке:

Если их описать как похожие, это не означает, что они идентичны во всех деталях. std :: pair и std :: tuple - это разные классы с разными требованиями к каждому. Если вы считаете, что они должны вести себя идентично в этом отношении (т.е. иметь свои подобъекты, определенные в одном и том же порядке), вам необходимо указать конкретную формулировку, которая гарантирует это.

person Arunmu    schedule 21.08.2016
comment
Откуда вы знаете, что порядок уничтожения элементов std::pair обратный? - person masoud; 21.08.2016
comment
Подобный! = Идентичный. - person 101010; 21.08.2016
comment
@deepmax На основе реализации как в libcxx, так и в libstd ++ для std :: pair. - person Arunmu; 21.08.2016
comment
@juanchopanza Разве создание пары не обязательно означает создание экземпляров ее членов? - person Arunmu; 21.08.2016
comment
Восстановите этот ответ вокруг информации в ссылке, пожалуйста! - person Yakk - Adam Nevraumont; 21.08.2016
comment
@juanchopanza Ага, поэтому я думаю, что формулировка similar в стандарте здесь немного сбивает с толку .. можно ли спорить обоими способами (?), не уверен. - person Arunmu; 21.08.2016
comment
@ 101010 Я не знаю, я думаю, что идентичное похоже на подобное. - person Yakk - Adam Nevraumont; 24.08.2016
comment
@yakk очень похож на идентичный, но не идентичен аналогичному :) - person 101010; 24.08.2016